/* * Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cheng Fang - Initial API and implementation */ package org.jberet.support.io; import java.lang.reflect.Constructor; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import org.jberet.support._private.SupportLogger; import org.jberet.support._private.SupportMessages; import org.supercsv.cellprocessor.CellProcessorAdaptor; import org.supercsv.cellprocessor.ConvertNullTo; import org.supercsv.cellprocessor.FmtBool; import org.supercsv.cellprocessor.FmtDate; import org.supercsv.cellprocessor.FmtNumber; import org.supercsv.cellprocessor.Optional; import org.supercsv.cellprocessor.ParseBigDecimal; import org.supercsv.cellprocessor.ParseBool; import org.supercsv.cellprocessor.ParseChar; import org.supercsv.cellprocessor.ParseDate; import org.supercsv.cellprocessor.ParseDouble; import org.supercsv.cellprocessor.ParseInt; import org.supercsv.cellprocessor.ParseLong; import org.supercsv.cellprocessor.StrReplace; import org.supercsv.cellprocessor.Token; import org.supercsv.cellprocessor.Trim; import org.supercsv.cellprocessor.Truncate; import org.supercsv.cellprocessor.constraint.DMinMax; import org.supercsv.cellprocessor.constraint.Equals; import org.supercsv.cellprocessor.constraint.ForbidSubStr; import org.supercsv.cellprocessor.constraint.IsElementOf; import org.supercsv.cellprocessor.constraint.IsIncludedIn; import org.supercsv.cellprocessor.constraint.LMinMax; import org.supercsv.cellprocessor.constraint.NotNull; import org.supercsv.cellprocessor.constraint.RequireHashCode; import org.supercsv.cellprocessor.constraint.RequireSubStr; import org.supercsv.cellprocessor.constraint.StrMinMax; import org.supercsv.cellprocessor.constraint.StrNotNullOrEmpty; import org.supercsv.cellprocessor.constraint.StrRegEx; import org.supercsv.cellprocessor.constraint.Strlen; import org.supercsv.cellprocessor.constraint.Unique; import org.supercsv.cellprocessor.constraint.UniqueHashCode; import org.supercsv.cellprocessor.ift.BoolCellProcessor; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.cellprocessor.ift.DateCellProcessor; import org.supercsv.cellprocessor.ift.DoubleCellProcessor; import org.supercsv.cellprocessor.ift.LongCellProcessor; import org.supercsv.cellprocessor.ift.StringCellProcessor; import org.supercsv.util.CsvContext; /** * This class is responsible for parsing the cellProcessors configuration property value into an array of * {@code org.supercsv.cellprocessor.ift.CellProcessor}, which can be consumed by * {@code org.jberet.support.io.CsvItemReader}. * * @since 1.0.0 */ final class CellProcessorConfig { static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * Parses the property value for cellProcessors into an array of {@code CellProcessor}. The number of the * returned {@code CellProcessor} must equal to the number of headers. * * @param val the raw property value. For example, * value = " * null * Optional, StrMinMax(1, 20) * ParseLong() * NotNull * ParseDate( 'dd/MM/yyyy' ) * StrMinMax(1, 20) * Optional, StrMinMax(1, 20), ParseDate('dd/MM/yyyy') * " * @return an array of {@code CellProcessor}, one for each line in the raw property value */ static CellProcessor[] parseCellProcessors(final String val) { //final String[] parts = val.split("\\r?\\n"); //new line final String[] parts = val.split(";"); final CellProcessor[] result = new CellProcessor[parts.length]; for (int x = 0; x < parts.length; x++) { // start parsing all lines final String line = parts[x].trim(); final char[] chars = line.toCharArray(); final List<List<String>> processorValuesInThisLine = new ArrayList<List<String>>(); List<String> oneProcessorValue = new ArrayList<String>(); int processorStartPosition = 0; int paramStartPosition = 0; byte insideParams = 0; byte insideQuote = 0; // start parsing a line int i; for (i = 0; i < chars.length; i++) { final char ch = chars[i]; switch (ch) { case '(': if (insideQuote == 0) { if (insideParams == 0) { insideParams++; // add the processor name as the first element final String s = line.substring(processorStartPosition, i).trim(); if (!s.isEmpty()) { oneProcessorValue.add(s); } paramStartPosition = i + 1; } else { throw SupportMessages.MESSAGES.unexpectedChar(ch, i, line); } } break; case ')': if (insideQuote == 0 || (insideQuote == 1 && i == chars.length - 1)) { if (insideParams == 1) { insideParams--; //end of param addParam(line, paramStartPosition, i, oneProcessorValue); //end of current processor endCurrentProcessor(line, processorStartPosition, i, oneProcessorValue, processorValuesInThisLine, true); processorStartPosition = i + 1; oneProcessorValue = new ArrayList<String>(); } else { throw SupportMessages.MESSAGES.unexpectedChar(ch, i, line); } } break; case '\'': if (insideQuote == 0) { insideQuote++; } else if (insideQuote == 1) { insideQuote--; } else { throw SupportMessages.MESSAGES.unexpectedChar(ch, i, line); } break; case ',': if (insideQuote == 0) { if (insideParams == 0) { //end of current processor endCurrentProcessor(line, processorStartPosition, i, oneProcessorValue, processorValuesInThisLine, false); processorStartPosition = i + 1; oneProcessorValue = new ArrayList<String>(); } else if (insideParams == 1) { // add the param to the current processor addParam(line, paramStartPosition, i, oneProcessorValue); paramStartPosition = i + 1; } else { throw SupportMessages.MESSAGES.unexpectedChar(ch, i, line); } } break; } } //end parsing a line if (insideQuote == 1) { SupportLogger.LOGGER.maybeMissingEndQuote(line); } if (processorStartPosition < i) { oneProcessorValue.add(line.substring(processorStartPosition, i).trim()); } if (!oneProcessorValue.isEmpty() && !processorValuesInThisLine.contains(oneProcessorValue)) { processorValuesInThisLine.add(oneProcessorValue); } result[x] = createCellProcessorForOneLine(processorValuesInThisLine); } //end parsing all lines return result; } private static void endCurrentProcessor(final String line, final int processorStartPosition, final int currentPosition, final List<String> oneProcessorValue, final List<List<String>> processorValuesInThisLine, final boolean endsWithParenthesis) { //if endsWithParenthesis, the processor name and params have already been recorded when ) is encountered, so //skip the following step. if (!endsWithParenthesis) { final String s = line.substring(processorStartPosition, currentPosition).trim(); if (!s.isEmpty()) { oneProcessorValue.add(s); } } if (!oneProcessorValue.isEmpty()) { processorValuesInThisLine.add(oneProcessorValue); } } private static void addParam(final String line, final int paramStartPosition, final int currentPosition, final List<String> oneProcessorValue) { String s = line.substring(paramStartPosition, currentPosition).trim(); if (!s.isEmpty()) { if (s.startsWith("'")) { if (s.endsWith("'")) { s = s.substring(1, s.length() - 1); } else { SupportLogger.LOGGER.maybeMissingEndQuote(line); s = s.substring(1, s.length()); } } oneProcessorValue.add(s); } } static CellProcessor createCellProcessorForOneLine(final List<List<String>> processorValuesInThisLine) { CellProcessor previous; CellProcessor current = null; for (int x = processorValuesInThisLine.size() - 1; x >= 0; x--) { previous = current; current = null; final List<String> oneProcessorValue = processorValuesInThisLine.get(x); SupportLogger.LOGGER.tracef("About to create CSV CellProcessor from %s%n", oneProcessorValue); final String name = oneProcessorValue.get(0); final String[] params; if (oneProcessorValue.size() == 1) { params = EMPTY_STRING_ARRAY; } else { params = new String[oneProcessorValue.size() - 1]; for (int i = 1; i < oneProcessorValue.size(); i++) { params[i - 1] = oneProcessorValue.get(i); } } //not supported CellProcessor: Collector, HashMapper, if (name.equalsIgnoreCase("null")) { current = null; break; } else if (name.equalsIgnoreCase("NotNull")) { if (params.length > 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } current = previous == null ? new NotNull() : new NotNull(previous); } else if (name.equalsIgnoreCase("Optional")) { if (params.length > 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } current = previous == null ? new Optional() : new Optional(previous); } else if (name.equalsIgnoreCase("ParseBool")) { if (params.length == 0) { //use the default true and false string values in org.supercsv.cellprocessor.ParseBool current = previous == null ? new ParseBool() : new ParseBool((BoolCellProcessor) previous); } else if (params.length == 2) { //use custom true and false values: can be either single value or multiple values final String[] trueValues = params[0].trim().split("\\s*,\\s*"); final String[] falseValues = params[1].trim().split("\\s*,\\s*"); current = previous == null ? new ParseBool(trueValues, falseValues) : new ParseBool(trueValues, falseValues, (BoolCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseChar")) { if (params.length > 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } current = previous == null ? new ParseChar() : new ParseChar((DoubleCellProcessor) previous); } else if (name.equalsIgnoreCase("ParseDate")) { if (params.length == 1) { //dateFormat current = previous == null ? new ParseDate(params[0]) : new ParseDate(params[0], (DateCellProcessor) previous); } else if (params.length == 2) { //dateFormat, lenient current = previous == null ? new ParseDate(params[0], Boolean.parseBoolean(params[1])) : new ParseDate(params[0], Boolean.parseBoolean(params[1]), (DateCellProcessor) previous); } else if (params.length == 3) { //dateFormat, lenient, locale current = previous == null ? new ParseDate(params[0], Boolean.parseBoolean(params[1]), new Locale(params[2])) : new ParseDate(params[0], Boolean.parseBoolean(params[1]), new Locale(params[2]), (DateCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseDouble")) { if (params.length == 0) { current = previous == null ? new ParseDouble() : new ParseDouble((DoubleCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseInt")) { if (params.length == 0) { current = previous == null ? new ParseInt() : new ParseInt((LongCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseLong")) { if (params.length == 0) { current = previous == null ? new ParseLong() : new ParseLong((LongCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseBigDecimal")) { if (params.length == 0) { current = previous == null ? new ParseBigDecimal() : new ParseBigDecimal(previous); } else if (params.length == 1) { //locale for getting DecimalFormatSymbols current = previous == null ? new ParseBigDecimal(new DecimalFormatSymbols(new Locale(params[0]))) : new ParseBigDecimal(new DecimalFormatSymbols(new Locale(params[0])), previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Truncate")) { if (params.length == 1) { //max length current = previous == null ? new Truncate(Integer.parseInt(params[0])) : new Truncate(Integer.parseInt(params[0]), (StringCellProcessor) previous); } else if (params.length == 2) { //max length, suffix current = previous == null ? new Truncate(Integer.parseInt(params[0]), params[1]) : new Truncate(Integer.parseInt(params[0]), params[1], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Trim")) { if (params.length == 0) { current = previous == null ? new Trim() : new Trim((StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Token")) { if (params.length == 2) { //token, replacement current = previous == null ? new Token(params[0], params[1]) : new Token(params[0], params[1], previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("StrReplace")) { if (params.length == 2) { //regex, replacement current = previous == null ? new StrReplace(params[0], params[1]) : new StrReplace(params[0], params[1], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ConvertNullTo")) { if (params.length == 1) { current = previous == null ? new ConvertNullTo(params[0]) : new ConvertNullTo(params[0], previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("FmtNumber")) { if (params.length == 1) { //decimalFormat current = previous == null ? new FmtNumber(params[0]) : new FmtNumber(params[0], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("FmtDate")) { if (params.length == 1) { //dateFormat current = previous == null ? new FmtDate(params[0]) : new FmtDate(params[0], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("FmtBool")) { if (params.length == 2) { //trueValue, falseValue current = previous == null ? new FmtBool(params[0], params[1]) : new FmtBool(params[0], params[1], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("DMinMax")) { if (params.length == 2) { //min, max current = previous == null ? new DMinMax(Double.parseDouble(params[0]), Double.parseDouble(params[1])) : new DMinMax(Double.parseDouble(params[0]), Double.parseDouble(params[1]), (DoubleCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Equals")) { if (params.length == 0) { //all input date equal to each other current = previous == null ? new Equals() : new Equals(previous); } else if (params.length == 1) { // equals to a supplied constantValue current = previous == null ? new Equals(params[0]) : new Equals(params[0], previous); } } else if (name.equalsIgnoreCase("ForbidSubStr")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } current = previous == null ? new ForbidSubStr(params) : new ForbidSubStr(params, previous); } else if (name.equalsIgnoreCase("IsElementOf")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } final Collection<Object> coll = new ArrayList<Object>(); Collections.addAll(coll, params); current = previous == null ? new IsElementOf(coll) : new IsElementOf(coll, previous); } else if (name.equalsIgnoreCase("IsIncludedIn")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } current = previous == null ? new IsIncludedIn(params) : new IsIncludedIn(params, previous); } else if (name.equalsIgnoreCase("LMinMax")) { if (params.length == 2) { current = previous == null ? new LMinMax(Long.parseLong(params[0]), Long.parseLong(params[1])) : new LMinMax(Long.parseLong(params[0]), Long.parseLong(params[1]), (LongCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("RequireHashCode")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } else if (params.length == 1) { current = previous == null ? new RequireHashCode(Integer.parseInt(params[0])) : new RequireHashCode(Integer.parseInt(params[0]), previous); } else { current = previous == null ? new RequireHashCode(CsvItemReader.convertToIntParams(params, 0, params.length)) : new RequireHashCode(CsvItemReader.convertToIntParams(params, 0, params.length), previous); } } else if (name.equalsIgnoreCase("RequireSubStr")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } if (params.length == 1) { current = previous == null ? new RequireSubStr(params[0]) : new RequireSubStr(params[0], previous); } else { current = previous == null ? new RequireSubStr(params) : new RequireSubStr(params, previous); } } else if (name.equalsIgnoreCase("Strlen")) { if (params.length == 0) { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } if (params.length == 1) { current = previous == null ? new Strlen(Integer.parseInt(params[0])) : new Strlen(Integer.parseInt(params[0]), previous); } else { current = previous == null ? new Strlen(CsvItemReader.convertToIntParams(params, 0, params.length)) : new Strlen(CsvItemReader.convertToIntParams(params, 0, params.length), previous); } } else if (name.equalsIgnoreCase("StrMinMax")) { if (params.length == 2) { // min, max current = previous == null ? new StrMinMax(Long.parseLong(params[0]), Long.parseLong(params[1])) : new StrMinMax(Long.parseLong(params[0]), Long.parseLong(params[1]), previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("StrNotNullOrEmpty")) { if (params.length == 0) { current = previous == null ? new StrNotNullOrEmpty() : new StrNotNullOrEmpty(previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("StrRegEx")) { if (params.length == 1) { current = previous == null ? new StrRegEx(params[0]) : new StrRegEx(params[0], (StringCellProcessor) previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Unique")) { if (params.length == 0) { current = previous == null ? new Unique() : new Unique(previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("UniqueHashCode")) { if (params.length == 0) { current = previous == null ? new UniqueHashCode() : new UniqueHashCode(previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("ParseEnum")) { if (params.length == 1) { current = previous == null ? new ParseEnum(params[0]) : new ParseEnum(params[0], previous); } else { throw SupportMessages.MESSAGES.invalidParamsForCellProcessor(name, params); } } else if (name.equalsIgnoreCase("Collector") || name.equalsIgnoreCase("HashMapper")) { throw SupportMessages.MESSAGES.unsupportedCellProcessor(name, params); } else { //custom cell processor current = createCustomCellProcessor(name, params, oneProcessorValue, previous); } } return current; } private static CellProcessor createCustomCellProcessor(final String name, final String[] params, final List<String> oneProcessorValue, final CellProcessor previous) { final Class[] constructorParamTypes; final CellProcessor result; try { final Class<?> cellProcessorClass = CellProcessorConfig.class.getClassLoader().loadClass(name); if (params.length == 0) { if (previous == null) { result = (CellProcessor) cellProcessorClass.newInstance(); } else { constructorParamTypes = new Class[] {CellProcessor.class}; final Constructor<?> constructor = cellProcessorClass.getConstructor(constructorParamTypes); result = (CellProcessor) constructor.newInstance(previous); } } else if (params.length == 1) { if (previous == null) { constructorParamTypes = CsvItemReaderWriterBase.stringParameterTypes; final Constructor<?> constructor = cellProcessorClass.getConstructor(constructorParamTypes); result = (CellProcessor) constructor.newInstance(params[0]); } else { constructorParamTypes = new Class[]{String.class, CellProcessor.class}; final Constructor<?> constructor = cellProcessorClass.getConstructor(constructorParamTypes); result = (CellProcessor) constructor.newInstance(params[0], previous); } } else { if (previous == null) { constructorParamTypes = new Class[]{String[].class}; final Constructor<?> constructor = cellProcessorClass.getConstructor(constructorParamTypes); result = (CellProcessor) constructor.newInstance(params); } else { constructorParamTypes = new Class[]{String[].class, CellProcessor.class}; final Constructor<?> constructor = cellProcessorClass.getConstructor(constructorParamTypes); result = (CellProcessor) constructor.newInstance(params, previous); } } } catch (final Exception e) { throw SupportMessages.MESSAGES.failToLoadOrCreateCustomType(e, oneProcessorValue.toString()); } return result; } /** * A custom cell processor that parses the cell data to enum. */ static final class ParseEnum extends CellProcessorAdaptor { private final String enumType; /** * Creates {@code ParseEnum} cell processor with a fully-qualified name of the enum type. For inner class enum, * use $ to separate outer class and inner type. * @param enumType the fully-qualified name of the enum type, e.g., org.jberet.support.io.HallOfFame$Category */ ParseEnum(final String enumType) { super(); this.enumType = enumType; } /** * Creates {@code ParseEnum} cell processor with a fully-qualified name of the enum type, and next cell processor. * For inner class enum, use $ to separate outer class and inner type. * @param enumType the fully-qualified name of the enum type, e.g., org.jberet.support.io.HallOfFame$Category * @param next next cell processor in the chain */ ParseEnum(final String enumType, final CellProcessor next) { super(next); this.enumType = enumType; } @Override public Object execute(final Object value, final CsvContext context) { validateInputNotNull(value, context); try { final Class<?> aClass = this.getClass().getClassLoader().loadClass(enumType); if (aClass.isEnum()) { final Object[] enumConstants = aClass.getEnumConstants(); for (final Object e : enumConstants) { if (value.equals(e.toString())) { return next.execute(e, context); } } } } catch (final Exception e) { throw SupportMessages.MESSAGES.failToParseEnum(e, value, enumType, context, this); } throw SupportMessages.MESSAGES.failToParseEnum(null, value, enumType, context, this); } } }