Utils.java
package com.gh.mygreen.xlsmapper.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import org.apache.poi.ss.usermodel.Sheet;
import com.gh.mygreen.xlsmapper.BeanFactory;
import com.gh.mygreen.xlsmapper.Configuration;
import com.gh.mygreen.xlsmapper.XlsMapperException;
import com.gh.mygreen.xlsmapper.annotation.XlsTrim;
import com.gh.mygreen.xlsmapper.cellconverter.DefaultElementConverter;
import com.gh.mygreen.xlsmapper.cellconverter.ElementConverter;
import com.gh.mygreen.xlsmapper.fieldprocessor.ProcessCase;
import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
/**
* ユーティリティクラス。
*
* @version 2.0
* @author T.TSUCHIE
* @author Naoki Takezoe
* @author Mitsuyoshi Hasegawa
*
*/
public class Utils {
@SuppressWarnings("rawtypes")
private static final ElementConverter ELEMENT_CONVERTER = new DefaultElementConverter();
/**
* コレクションの要素を指定した区切り文字で繋げて1つの文字列とする。
* @param col 処理対象のコレクション。
* @param separator 区切り文字。
* @param ignoreEmptyElement 空、nullの要素を無視するかどうか。
* @param trim トリムをするかどうか。
* @param elementConverter 要素を変換するクラス。
* @return 結合した文字列
*/
@SuppressWarnings("rawtypes")
public static String join(final Collection<?> col, final String separator,
final boolean ignoreEmptyElement, final boolean trim, final ElementConverter elementConverter) {
final List<Object> list = new ArrayList<Object>();
for(Object element : col) {
if(element == null) {
continue;
}
Object value = element;
if(element instanceof String) {
String str = (String) element;
if(ignoreEmptyElement && isEmpty(str)) {
continue;
} else if(trim) {
value = str.trim();
}
} else if(element instanceof Character && isEmpty(element.toString())) {
String str = element.toString();
if(ignoreEmptyElement && isEmpty(str)) {
continue;
} else if(trim) {
value = str.trim().charAt(0);
}
} else if(char.class.isAssignableFrom(element.getClass())) {
String str = element.toString();
if(ignoreEmptyElement && isEmpty(str)) {
continue;
} else if(trim) {
value = str.trim().charAt(0);
}
}
list.add(value);
}
return join(list, separator, elementConverter);
}
/**
* 配列の要素を指定した区切り文字で繋げて1つの文字列とする。
* @param arrays 結合対象の配列
* @param separator 区切り文字
* @return 結合した文字列
*/
public static String join(final Object[] arrays, final String separator) {
return join(arrays, separator, ELEMENT_CONVERTER);
}
/**
* 配列の要素を指定した区切り文字で繋げて1つの文字列とする。
* @param arrays 結合対象の配列
* @param separator 区切り文字
* @param elementConverter 要素を変換するクラス。
* @return 結合した文字列
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static String join(final Object[] arrays, final String separator, final ElementConverter elementConverter) {
if(arrays == null) {
return "";
}
final int len = arrays.length;
if(len == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for(int i=0; i < len; i++) {
final Object element = arrays[i];
sb.append(elementConverter.convertToString(element));
if(separator != null && (i < len-1)) {
sb.append(separator);
}
}
return sb.toString();
}
/**
* Collectionの要素を指定した区切り文字で繋げて1つの文字列とする。
* @param col 結合対象のコレクション
* @param separator 区切り文字
* @return 結合した文字列
*/
public static String join(final Collection<?> col, final String separator) {
return join(col, separator, ELEMENT_CONVERTER);
}
/**
* Collectionの要素を指定した区切り文字で繋げて1つの文字列とする。
* @param col 結合対象のコレクション
* @param separator 区切り文字
* @param elementConverter 要素を変換するクラス。
* @return 結合した文字列
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static String join(final Collection<?> col, final String separator, final ElementConverter elementConverter) {
if(col == null) {
return "";
}
final int size = col.size();
if(size == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for(Iterator<?> itr = col.iterator(); itr.hasNext();) {
final Object element = itr.next();
String text = elementConverter.convertToString(element);
sb.append(text);
if(separator != null && itr.hasNext()) {
sb.append(separator);
}
}
return sb.toString();
}
/**
* 先頭の文字を大文字にする。
* <pre>
* Utils.capitalize(null) = null
* Utils.capitalize("") = ""
* Utils.capitalize("cat") = "Cat"
* Utils.capitalize("cAt") = "CAt"
* </pre>
* @param str
* @return 引数がnull、空文字の場合、そのまま返す。
*/
public static String capitalize(final String str) {
final int strLen;
if(str == null || (strLen = str.length()) == 0) {
return str;
}
return new StringBuilder(strLen)
.append(String.valueOf(str.charAt(0)).toUpperCase())
.append(str.substring(1))
.toString();
}
/**
* 先頭の文字を小文字にする。
* @param str 変換対象の文字
* @return 引数がnull、空文字の場合、そのまま返す。
*/
public static String uncapitalize(final String str) {
final int strLen;
if(str == null || (strLen = str.length()) == 0) {
return str;
}
return new StringBuilder(strLen)
.append(String.valueOf(str.charAt(0)).toLowerCase())
.append(str.substring(1))
.toString();
}
/**
* システム設定に従いラベルを比較する。
* <p>正規表現や正規化を行い指定する。
*
* @since 1.1
* @param text1 セルのラベル
* @param text2 アノテーションに指定されているラベル。
* {@literal /<ラベル>/}と指定する場合、正規表現による比較を行う。
* @param config システム設定
* @return true:ラベルが一致する。比較対象のラベルがnullの場合は、falseを返す。
*/
public static boolean matches(final String text1, final String text2, final Configuration config){
if(text1 == null || text2 == null) {
return false;
}
if(config.isRegexLabelText() && text2.startsWith("/") && text2.endsWith("/")){
return normalize(text1, config).matches(text2.substring(1, text2.length() - 1));
} else {
return normalize(text1, config).equals(normalize(text2, config));
// return normalize(text1, config).equals(text2);
}
}
/**
* システム設定に従いラベルを正規化する。
* @since 1.1
* @param text セルのラベル
* @param config システム設定
* @return true:ラベルが一致する。
*/
public static String normalize(final String text, final Configuration config){
if(text != null && config.isNormalizeLabelText()){
return text.trim().replaceAll("[\n\r]", "").replaceAll("[\t ]+", " ");
}
return text;
}
/**
* 文字列が空文字か判定する。
* <p>文字数が1でかつ、{@literal \u0000}のときは、trueを判定する。</p>
* @param str 判定対象の文字
* @return trueの場合、空文字を判定する。
*/
public static boolean isEmpty(final String str) {
if(str == null || str.isEmpty()) {
return true;
}
if(str.length() == 1) {
return str.charAt(0) == '\u0000';
}
return false;
}
/**
* 文字列が空文字でないか判定する。
* @param str 判定対象の文字
* @return
*/
public static boolean isNotEmpty(final String str) {
return !isEmpty(str);
}
/**
* コレクションが空か判定する。
* @param collection
* @return nullまたはサイズが0のときにtrueを返す。
*/
public static boolean isEmpty(final Collection<?> collection) {
if(collection == null || collection.isEmpty()) {
return true;
}
return false;
}
public static boolean isNotEmpty(final Collection<?> collection) {
return !isEmpty(collection);
}
/**
* 配列がが空か判定する。
* @param arrays
* @return nullまたは、配列のサイズが0のときにtrueを返す。
*/
public static boolean isEmpty(final Object[] arrays) {
if(arrays == null || arrays.length == 0) {
return true;
}
return false;
}
/**
* 配列が空でないか判定する
* @param arrays
* @return
*/
public static boolean isNotEmpty(final Object[] arrays) {
return !isEmpty(arrays);
}
/**
* オブジェクトの比較を行う。
* <p>値がnullの場合を考慮する。
* @param obj1
* @param obj2
* @return
*/
public static boolean equals(final Object obj1, final Object obj2) {
if(obj1 == null && obj2 == null) {
return true;
}
if(obj1 == null) {
return false;
}
if(obj2 == null) {
return false;
}
return obj1.equals(obj2);
}
public static boolean notEquals(final Object obj1, final Object obj2) {
return !equals(obj1, obj2);
}
/**
* オブジェクトを文字列に変換する。
* <p>nullの場合、文字列として "null"を返す。
* <p>単純に、{@link Object#toString()}を呼び出す。
* @param value
* @return
*/
public static String convertToString(final Object value) {
if(value == null) {
return "null";
}
return value.toString();
}
/**
* アノテーションの属性trimに従い、文字列をトリムする。
* @param value トリム対象の文字
* @param trimAnno トリムのアノテーション
* @return トリミングした結果。
*/
public static String trim(final String value, final Optional<XlsTrim> trimAnno) {
if(!trimAnno.isPresent() || value == null) {
return value;
}
return value.trim();
}
/**
* 文字列をトリムする。
* @param value トリム対象の文字
* @param trimmed トリムするかどうか。
* @return トリミングした結果。
*/
public static String trim(final String value, final boolean trimmed) {
if(!trimmed || value == null) {
return value;
}
return value.trim();
}
/**
* PostProcessなどのメソッドを実行する。
* <p>メソッドの引数が既知のものであれば、インスタンスを設定する。
*
* @param processObj 実行対象の処理が埋め込まれているオブジェクト。
* @param method 実行対象のメソッド情報
* @param beanObj 処理対象のBeanオブジェクト。
* @param sheet シート情報
* @param config 共通設定
* @param errors エラー情報
* @param processCase 処理ケース
* @throws XlsMapperException
*/
public static void invokeNeedProcessMethod(final Object processObj, final Method method, final Object beanObj,
final Sheet sheet, final Configuration config, final SheetBindingErrors<?> errors, final ProcessCase processCase)
throws XlsMapperException {
final Class<?>[] paramTypes = method.getParameterTypes();
final Object[] paramValues = new Object[paramTypes.length];
for(int i=0; i < paramTypes.length; i++) {
if(Sheet.class.isAssignableFrom(paramTypes[i])) {
paramValues[i] = sheet;
} else if(Configuration.class.isAssignableFrom(paramTypes[i])) {
paramValues[i] = config;
} else if(SheetBindingErrors.class.isAssignableFrom(paramTypes[i])) {
paramValues[i] = errors;
} else if(paramTypes[i].isAssignableFrom(beanObj.getClass())) {
paramValues[i] = beanObj;
} else if(ProcessCase.class.equals(paramTypes[i])) {
paramValues[i] = processCase;
} else if(paramTypes[i].equals(Object.class)) {
paramValues[i] = beanObj;
} else {
paramValues[i] = null;
}
}
try {
method.setAccessible(true);
method.invoke(processObj, paramValues);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
Throwable t = e.getCause() == null ? e : e.getCause();
throw new XlsMapperException(
String.format("fail execute method '%s#%s'.", processObj.getClass().getName(), method.getName()),
t);
}
}
/**
* 文字列形式のロケールをオブジェクトに変換する。
* <p>アンダーバーで区切った'ja_JP'を分解して、Localeに渡す。
* @param str
* @return 引数が空の時はデフォルトロケールを返す。
*/
public static Locale getLocale(final String str) {
if(isEmpty(str)) {
return Locale.getDefault();
}
if(!str.contains("_")) {
return new Locale(str);
}
final String[] split = str.split("_");
if(split.length == 2) {
return new Locale(split[0], split[1]);
} else {
return new Locale(split[0], split[1], split[2]);
}
}
/**
* エスケープ文字を除去した文字列を取得する。
* @param str
* @param escapeChar
* @return
*/
public static String removeEscapeChar(final String str, final char escapeChar) {
if(str == null || str.isEmpty()) {
return str;
}
final String escapeStr = String.valueOf(escapeChar);
StringBuilder sb = new StringBuilder();
LinkedList<String> stack = new LinkedList<String>();
final int length = str.length();
for(int i=0; i < length; i++) {
final char c = str.charAt(i);
if(StackUtils.equalsTopElement(stack, escapeStr)) {
// スタックの一番上がエスケープ文字の場合
StackUtils.popup(stack);
sb.append(c);
} else if(c == escapeChar) {
// スタックに積む
stack.push(String.valueOf(c));
} else {
sb.append(c);
}
}
if(!stack.isEmpty()) {
sb.append(StackUtils.popupAndConcat(stack));
}
return sb.toString();
}
/**
* Listのインスタンスを他のCollectionのインスタンスに変換する。
* <p>ただし、変換先のクラスタイプがインタフェースの場合は変換しない。
* <p>変換元のクラスと変換先のクラスが同じ場合は、変換しない。
*
* @since 1.0
* @param list 変換元のListのインスタンス
* @param toClass 変換先のCollectionのクラス
* @param beanFactory てインスタンスを生成するファクトリクラス。
* @return 変換したコレクションのインスタンス
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Collection convertListToCollection(final List list, final Class<Collection> toClass,
final BeanFactory<Class<?>, Object> beanFactory) {
if(list.getClass().equals(toClass)) {
return list;
}
if(toClass.isInterface()) {
if(List.class.isAssignableFrom(toClass)) {
// 変換先がListの実態の場合はそのまま。
return list;
} else if(Set.class.isAssignableFrom(toClass)) {
Collection value = (Collection) beanFactory.create(LinkedHashSet.class);
value.addAll(list);
return value;
} else if(Queue.class.isAssignableFrom(toClass)) {
Collection value = (Collection) beanFactory.create(LinkedList.class);
value.addAll(list);
return value;
} else if(Collection.class.isAssignableFrom(toClass)) {
Collection value = (Collection) beanFactory.create(ArrayList.class);
value.addAll(list);
return value;
} else {
throw new IllegalArgumentException("not support class type:" + toClass.getName());
}
}
Collection value = (Collection) beanFactory.create(toClass);
value.addAll(list);
return value;
}
/**
* CollectionのインスタンスをListに変換する。
*
* @since 1.0
* @param collection 変換元のCollectionのインスタンス。
* @return 変換したListのインスタンス。
*/
public static <T> List<T> convertCollectionToList(final Collection<T> collection) {
if(List.class.isAssignableFrom(collection.getClass())) {
return (List<T>)collection;
}
return new ArrayList<>(collection);
}
/**
* リストに要素のインデックスを指定して追加します。
* <p>リストのサイズが足りない場合は、サイズを自動的に変更します。</p>
* @since 2.0
* @param list リスト
* @param element 追加する要素。値はnullでもよい。
* @param index 追加する要素のインデックス番号(0以上)
* @throws IllegalArgumentException {@literal list == null.}
* @throws IllegalArgumentException {@literal index < 0.}
*/
public static <P> void addListWithIndex(final List<P> list, final P element, final int index) {
ArgUtils.notNull(list, "list");
ArgUtils.notMin(index, 0, "index");
final int listSize = list.size();
if(listSize < index) {
// 足りない場合は、要素を追加する。
final int lackSize = index - listSize;
for(int i=0; i < lackSize; i++) {
list.add(null);
}
list.add(element);
} else if(listSize == index) {
// 最後の要素に追加する
list.add(element);
} else {
// リストのサイズが足りている場合
list.set(index, element);
}
}
/**
* プリミティブ型のデフォルト値を取得します。
* @param type 変換対象のクラスタイプ。
* @return 対応していない型の場合は、nullを返します。
* @throws IllegalArgumentException {@literal type is null.}
*/
public static Object getPrimitiveDefaultValue(final Class<?> type) {
ArgUtils.notNull(type, "type");
if(!type.isPrimitive()) {
return null;
}
if(type.equals(boolean.class)) {
return false;
} else if(type.equals(char.class)) {
return '\u0000';
} else if(type.equals(byte.class)) {
return (byte)0;
} else if(type.equals(short.class)) {
return (short)0;
} else if(type.equals(int.class)) {
return 0;
} else if(type.equals(long.class)) {
return 0l;
} else if(type.equals(float.class)) {
return 0.0f;
} else if(type.equals(double.class)) {
return 0.0d;
}
return null;
}
/**
* 配列を{@link List}に変換します。
* プリミティブ型の配列をを考慮して処理します。
* @param object 変換対象の配列
* @param componentType 配列の要素のタイプ
* @return 配列がnullの場合は、空のリストに変換します。
* @throws IllegalArgumentException {@literal arrayが配列でない場合。componentTypeがサポートしていないプリミティブ型の場合。}
*/
public static List<Object> asList(final Object object, final Class<?> componentType) {
ArgUtils.notNull(componentType, "componentType");
if(object == null) {
return new ArrayList<>();
}
if(!object.getClass().isArray()) {
throw new IllegalArgumentException(String.format("args0 is not arrays : %s.", object.getClass().getName()));
}
if(!componentType.isPrimitive()) {
return Arrays.asList((Object[])object);
}
if(componentType.equals(boolean.class)) {
boolean[] array = (boolean[])object;
List<Object> list = new ArrayList<>(array.length);
for(boolean v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(char.class)) {
char[] array = (char[])object;
List<Object> list = new ArrayList<>(array.length);
for(char v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(byte.class)) {
byte[] array = (byte[])object;
List<Object> list = new ArrayList<>(array.length);
for(byte v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(short.class)) {
short[] array = (short[])object;
List<Object> list = new ArrayList<>(array.length);
for(short v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(int.class)) {
int[] array = (int[])object;
List<Object> list = new ArrayList<>(array.length);
for(int v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(long.class)) {
long[] array = (long[])object;
List<Object> list = new ArrayList<>(array.length);
for(long v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(float.class)) {
float[] array = (float[])object;
List<Object> list = new ArrayList<>(array.length);
for(float v : array) {
list.add(v);
}
return list;
} else if(componentType.equals(double.class)) {
double[] array = (double[])object;
List<Object> list = new ArrayList<>(array.length);
for(double v : array) {
list.add(v);
}
return list;
}
throw new IllegalArgumentException(String.format("not support primitive type : %s.", componentType.getName()));
}
/**
* コレクションを配列に変換する。
* @param collection 変換対象のコレクション。
* @return 変換した配列。
* @throws IllegalArgumentException {@literal collection is null.}
*/
public static int[] toArray(final Collection<Integer> collection) {
ArgUtils.notNull(collection, "collection");
final int size = collection.size();
final int[] array = new int[size];
int i=0;
for(Integer value : collection) {
array[i] = value;
i++;
}
return array;
}
/**
* 配列のサイズを取得します。
* プリミティブ型の配列をを考慮して処理します。
* @param object 取得対象の配列
* @param componentType 配列の要素のタイプ
* @return 配列がnullの場合は、0を返します。
* @throws IllegalArgumentException {@literal arrayが配列でない場合。componentTypeがサポートしていないプリミティブ型の場合。}
*/
public static int getArraySize(final Object object, final Class<?> componentType) {
ArgUtils.notNull(componentType, "componentType");
if(object == null) {
return 0;
}
if(!object.getClass().isArray()) {
throw new IllegalArgumentException(String.format("args0 is not arrays : %s.", object.getClass().getName()));
}
if(!componentType.isPrimitive()) {
return ((Object[])object).length;
}
if(componentType.equals(boolean.class)) {
return ((boolean[]) object).length;
} else if(componentType.equals(char.class)) {
return ((char[]) object).length;
} else if(componentType.equals(byte.class)) {
return ((byte[]) object).length;
} else if(componentType.equals(short.class)) {
return ((short[]) object).length;
} else if(componentType.equals(int.class)) {
return ((int[]) object).length;
} else if(componentType.equals(long.class)) {
return ((long[]) object).length;
} else if(componentType.equals(float.class)) {
return ((byte[]) object).length;
} else if(componentType.equals(double.class)) {
return ((byte[]) object).length;
}
throw new IllegalArgumentException(String.format("not support primitive type : %s.", componentType.getName()));
}
/**
* 文字列配列の結合
* @param array1
* @param array2
* @return 結合した配列。引数のどちらからnullの場合は、cloneした配列を返します。
*/
public static String[] concat(final String[] array1, final String[] array2) {
if(array1 == null || array1.length == 0) {
return clone(array2);
} else if(array2 == null || array2.length == 0) {
return clone(array1);
}
final String[] joinedArray = new String[array1.length + array2.length];
System.arraycopy(array1, 0, joinedArray, 0, array1.length);
System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
return joinedArray;
}
/**
* 文字列の配列をクローンします。
* @param array クローン対象の配列
* @return クローンした配列。引数がnullの場合は、nullを返します。
*/
public static String[] clone(final String[] array) {
if (array == null) {
return null;
}
return array.clone();
}
/**
* 読み込み処理のケースか判定する。
* <p>ケースが指定されていないときは、該当すると判定する。</p>
* @since 2.0
* @param cases 判定対象のケース
* @return trueのとき、読み込み対象と判定する。
*/
public static boolean isLoadCase(final ProcessCase[] cases) {
if(cases == null || cases.length == 0) {
return true;
}
for(ProcessCase pc : cases) {
if(pc == ProcessCase.Load) {
return true;
}
}
return false;
}
/**
* 書き込み処理のケースか判定する。
* <p>ケースが指定されていないときは、該当すると判定する。</p>
* @since 2.0
* @param cases 判定対象のケース
* @return trueのとき、書き込み対象と判定する。
*/
public static boolean isSaveCase(final ProcessCase[] cases) {
if(cases == null || cases.length == 0) {
return true;
}
for(ProcessCase pc : cases) {
if(pc == ProcessCase.Save) {
return true;
}
}
return false;
}
/**
* 現在の処理ケースが該当するか判定する。
* <p>ケースが指定されていないときは、該当すると判定する。</p>
* @param currentCase 現在の処理ケース
* @param cases 判定対象のケース
* @return trueのとき判定対象。
* @throws IllegalArgumentException {@code currentCase is null.}
*/
public static boolean isProcessCase(final ProcessCase currentCase, ProcessCase[] cases) {
ArgUtils.notNull(currentCase, "currentCase");
if(currentCase == ProcessCase.Load) {
return isLoadCase(cases);
} else if(currentCase == ProcessCase.Save) {
return isSaveCase(cases);
} else {
throw new IllegalArgumentException("currentCase is not support:" + currentCase);
}
}
}