All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.univocity.parsers.common.fields.FieldConversionMapping Maven / Gradle / Ivy

Go to download

univocity's open source parsers for processing different text formats using a consistent API

The newest version!
/*******************************************************************************
 * Copyright 2014 Univocity Software Pty Ltd
 *
 * 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 com.univocity.parsers.common.fields;

import com.univocity.parsers.annotations.helpers.*;
import com.univocity.parsers.common.*;
import com.univocity.parsers.conversions.*;

import java.util.*;

/**
 * A class for mapping field selections to sequences of {@link Conversion} objects
 *
 * @author Univocity Software Pty Ltd - [email protected]
 */
public class FieldConversionMapping implements Cloneable {

	@SuppressWarnings("rawtypes")
	private static final Conversion[] EMPTY_CONVERSION_ARRAY = new Conversion[0];

	public int[] validatedIndexes;

	/**
	 * This list contains the sequence of conversions applied to sets of fields over multiple calls.
	 * 

It is shared by {@link FieldConversionMapping#fieldNameConversionMapping}, {@link FieldConversionMapping#fieldIndexConversionMapping} and {@link FieldConversionMapping#convertAllMapping}. *

Every time the user associates a sequence of conversions to a field, conversionSequence list will receive the FieldSelector. */ private List conversionSequence = new ArrayList(); private AbstractConversionMapping fieldNameConversionMapping = new AbstractConversionMapping(conversionSequence) { @Override protected FieldSelector newFieldSelector() { return new FieldNameSelector(); } }; private AbstractConversionMapping fieldIndexConversionMapping = new AbstractConversionMapping(conversionSequence) { @Override protected FieldSelector newFieldSelector() { return new FieldIndexSelector(); } }; @SuppressWarnings("rawtypes") private AbstractConversionMapping fieldEnumConversionMapping = new AbstractConversionMapping(conversionSequence) { @Override protected FieldSelector newFieldSelector() { return new FieldEnumSelector(); } }; private AbstractConversionMapping convertAllMapping = new AbstractConversionMapping(conversionSequence) { @Override protected FieldSelector newFieldSelector() { return new AllIndexesSelector(); } }; /** * This is the final sequence of conversions applied to each index in a record. It is populated when {@link FieldConversionMapping#prepareExecution(boolean, String[])} is invoked. */ private Map>> conversionsByIndex = Collections.emptyMap(); private Map> validationsByIndex = Collections.emptyMap(); /** * Prepares the conversions registered in this object to be executed against a given sequence of fields * * @param writing flag indicating whether a writing process is being initialized. * @param values The field sequence that identifies how records will be organized. *

This is generally the sequence of headers in a record, but it might be just the first parsed row from a given input (as field selection by index is allowed). */ public void prepareExecution(boolean writing, String[] values) { if (fieldNameConversionMapping.isEmpty() && fieldEnumConversionMapping.isEmpty() && fieldIndexConversionMapping.isEmpty() && convertAllMapping.isEmpty()) { return; } if (!conversionsByIndex.isEmpty()) { return; } //Note this property is shared across all conversion mappings. This is required so //the correct conversion sequence is registered for all fields. conversionsByIndex = new HashMap>>(); // adds the conversions in the sequence they were created. for (FieldSelector next : conversionSequence) { fieldNameConversionMapping.prepareExecution(writing, next, conversionsByIndex, values); fieldIndexConversionMapping.prepareExecution(writing, next, conversionsByIndex, values); fieldEnumConversionMapping.prepareExecution(writing, next, conversionsByIndex, values); convertAllMapping.prepareExecution(writing, next, conversionsByIndex, values); } Iterator>>> entryIterator = conversionsByIndex.entrySet().iterator(); while (entryIterator.hasNext()) { Map.Entry>> e = entryIterator.next(); Iterator> it = e.getValue().iterator(); while (it.hasNext()) { Conversion conversion = it.next(); if (conversion instanceof ValidatedConversion) { if (validationsByIndex.isEmpty()) { validationsByIndex = new TreeMap>(); } it.remove(); List validations = validationsByIndex.get(e.getKey()); if (validations == null) { validations = new ArrayList(1); validationsByIndex.put(e.getKey(), validations); } validations.add((ValidatedConversion) conversion); } } if (e.getValue().isEmpty()) { entryIterator.remove(); } } validatedIndexes = ArgumentUtils.toIntArray(validationsByIndex.keySet()); } /** * Applies a sequence of conversions on all fields. * * @param conversions the sequence of conversions to be applied */ public void applyConversionsOnAllFields(Conversion... conversions) { convertAllMapping.registerConversions(conversions); } /** * Applies a sequence of conversions on a selection of field indexes * * @param conversions the sequence of conversions to be applied * * @return a selector of column indexes. */ public FieldSet applyConversionsOnFieldIndexes(Conversion... conversions) { return fieldIndexConversionMapping.registerConversions(conversions); } /** * Applies a sequence of conversions on a selection of field name * * @param conversions the sequence of conversions to be applied * * @return a selector of column names. */ public FieldSet applyConversionsOnFieldNames(Conversion... conversions) { return fieldNameConversionMapping.registerConversions(conversions); } /** * Applies a sequence of conversions on a selection of enumerations that represent fields * * @param conversions the sequence of conversions to be applied * * @return a selector of enumerations. */ @SuppressWarnings("rawtypes") public FieldSet applyConversionsOnFieldEnums(Conversion... conversions) { return fieldEnumConversionMapping.registerConversions(conversions); } /** * Applies any validations associated with a field at a given index in a record * @param index The index of parsed value in a record * @param value The value of the record at the given index */ public void executeValidations(int index, Object value) { List validations = validationsByIndex.get(index); if (validations != null) { for (int i = 0; i < validations.size(); i++) { validations.get(i).execute(value); } } } /** * Applies a sequence of conversions associated with an Object value at a given index in a record. * * @param executeInReverseOrder flag to indicate whether or not the conversion sequence must be executed in reverse order * @param index The index of parsed value in a record * @param value The value in a record * @param convertedFlags an array of flags that indicate whether a conversion occurred. Used to determine whether * or not a default conversion by type (specified with {@link ConversionProcessor#convertType(Class, Conversion[])}) should be applied. * * @return the Object resulting from a sequence of conversions against the original value. */ @SuppressWarnings({"unchecked", "rawtypes"}) public Object reverseConversions(boolean executeInReverseOrder, int index, Object value, boolean[] convertedFlags) { List> conversions = conversionsByIndex.get(index); if (conversions != null) { if (convertedFlags != null) { convertedFlags[index] = true; } Conversion conversion = null; try { if (executeInReverseOrder) { for (int i = conversions.size() - 1; i >= 0; i--) { conversion = conversions.get(i); value = conversion.revert(value); } } else { for (Conversion c : conversions) { conversion = c; value = conversion.revert(value); } } } catch (DataProcessingException ex) { ex.setValue(value); ex.setColumnIndex(index); ex.markAsNonFatal(); throw ex; } catch (Throwable ex) { DataProcessingException exception; if (conversion != null) { exception = new DataProcessingException("Error converting value '{value}' using conversion " + conversion.getClass().getName(), ex); } else { exception = new DataProcessingException("Error converting value '{value}'", ex); } exception.setValue(value); exception.setColumnIndex(index); exception.markAsNonFatal(); throw exception; } } return value; } /** * Applies a sequence of conversions associated with a String value parsed from a given index. * * @param index The index of parsed value in a record * @param stringValue The parsed value in a record * @param convertedFlags an array of flags that indicate whether a conversion occurred. Used to determine whether * or not a default conversion by type (specified with {@link ConversionProcessor#convertType(Class, Conversion[])}) should be applied. * * @return the Object produced by a sequence of conversions against the original String value. */ @SuppressWarnings({"rawtypes", "unchecked"}) public Object applyConversions(int index, String stringValue, boolean[] convertedFlags) { List> conversions = conversionsByIndex.get(index); if (conversions != null) { if (convertedFlags != null) { convertedFlags[index] = true; } Object result = stringValue; for (Conversion conversion : conversions) { try { result = conversion.execute(result); } catch (DataProcessingException ex) { ex.setColumnIndex(index); ex.markAsNonFatal(); throw ex; } catch (Throwable ex) { DataProcessingException exception = new DataProcessingException("Error converting value '{value}' using conversion " + conversion.getClass().getName(), ex); exception.setValue(result); exception.setColumnIndex(index); exception.markAsNonFatal(); throw exception; } } return result; } return stringValue; } /** * Returns the sequence of conversions to be applied at a given column index * * @param index the index of the column where the conversions should be executed * @param expectedType the type resulting from the conversion sequence. * * @return the sequence of conversions to be applied at a given column index */ @SuppressWarnings("rawtypes") public Conversion[] getConversions(int index, Class expectedType) { List> conversions = conversionsByIndex.get(index); Conversion[] out; if (conversions != null) { out = new Conversion[conversions.size()]; int i = 0; for (Conversion conversion : conversions) { out[i++] = conversion; } } else if (expectedType == String.class) { return EMPTY_CONVERSION_ARRAY; } else { out = new Conversion[1]; out[0] = AnnotationHelper.getDefaultConversion(expectedType, null, null); if (out[0] == null) { return EMPTY_CONVERSION_ARRAY; } } return out; } @Override public FieldConversionMapping clone() { try { FieldConversionMapping out = (FieldConversionMapping) super.clone(); out.validatedIndexes = validatedIndexes == null ? null : this.validatedIndexes.clone(); out.conversionSequence = new ArrayList(); Map clonedSelectors = new HashMap(); for (FieldSelector selector : this.conversionSequence) { FieldSelector clone = (FieldSelector) selector.clone(); out.conversionSequence.add(clone); clonedSelectors.put(selector, clone); } out.fieldNameConversionMapping = fieldNameConversionMapping.clone(clonedSelectors, out.conversionSequence); out.fieldIndexConversionMapping = fieldIndexConversionMapping.clone(clonedSelectors, out.conversionSequence); out.fieldEnumConversionMapping = fieldEnumConversionMapping.clone(clonedSelectors, out.conversionSequence); out.convertAllMapping = convertAllMapping.clone(clonedSelectors, out.conversionSequence); out.conversionsByIndex = new HashMap>>(conversionsByIndex); out.validationsByIndex = new TreeMap>(validationsByIndex); return out; } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } } /** * Class responsible for managing field selections and any conversion sequence associated with each. * * @param the FieldSelector type information used to uniquely identify a field (e.g. references to field indexes would use Integer, while references to field names would use String). * * @author Univocity Software Pty Ltd - [email protected] * @see FieldNameSelector * @see FieldIndexSelector */ abstract class AbstractConversionMapping implements Cloneable { private Map[]> conversionsMap; private List conversionSequence; AbstractConversionMapping(List conversionSequence) { this.conversionSequence = conversionSequence; } /** * Registers a sequence of conversions to a set of fields. *

The selector instance that is used to store which fields should be converted is added to the {@link AbstractConversionMapping#conversionSequence} list in order to keep track of the correct conversion order. *

This is required further conversion sequences might be added to the same fields in separate calls. * * @param conversions the conversion sequence to be applied to a set of fields. * * @return a FieldSet which provides methods to select the fields that must be converted or null if the FieldSelector returned by #newFieldSelector is not an instance of FieldSet (which is the case of {@link AllIndexesSelector}). */ @SuppressWarnings("unchecked") public FieldSet registerConversions(Conversion... conversions) { ArgumentUtils.noNulls("Conversions", conversions); FieldSelector selector = newFieldSelector(); if (conversionsMap == null) { conversionsMap = new LinkedHashMap[]>(); } conversionsMap.put(selector, conversions); conversionSequence.add(selector); if (selector instanceof FieldSet) { return (FieldSet) selector; } return null; } /** * Creates a FieldSelector instance of the desired type. Used in @link FieldConversionMapping}. * * @return a new FieldSelector instance. */ protected abstract FieldSelector newFieldSelector(); /** * Get all indexes in the given selector and adds the conversions defined at that index to the map of conversionsByIndex. *

This method is called in the same sequence each selector was created (in {@link FieldConversionMapping#prepareExecution(boolean, String[])}) *

At the end of the process, the map of conversionsByIndex will have each index with its list of conversions in the order they were declared. * * @param writing flag indicating whether a writing process is being initialized. * @param selector the selected fields for a given conversion sequence. * @param conversionsByIndex map of all conversions registered to every field index, in the order they were declared * @param values The field sequence that identifies how records will be organized. *

This is generally the sequence of headers in a record, but it might be just the first parsed row from a given input (as field selection by index is allowed). */ public void prepareExecution(boolean writing, FieldSelector selector, Map>> conversionsByIndex, String[] values) { if (conversionsMap == null) { return; } //conversionsMap contains maps the conversions applied to a field selection //we will match the indexes where these conversions where applied and add them to the corresponding list in conversionsByIndex Conversion[] conversions = conversionsMap.get(selector); if (conversions == null) { return; } int[] fieldIndexes = selector.getFieldIndexes(NormalizedString.toIdentifierGroupArray(values)); if (fieldIndexes == null) { fieldIndexes = ArgumentUtils.toIntArray(conversionsByIndex.keySet()); } for (int fieldIndex : fieldIndexes) { List> conversionsAtIndex = conversionsByIndex.get(fieldIndex); if (conversionsAtIndex == null) { conversionsAtIndex = new ArrayList>(); conversionsByIndex.put(fieldIndex, conversionsAtIndex); } validateDuplicates(selector, conversionsAtIndex, conversions); conversionsAtIndex.addAll(Arrays.asList(conversions)); } } /** * Ensures an individual field does not have the same conversion object applied to it more than once. * * @param selector the selection of fields * @param conversionsAtIndex the sequence of conversions applied to a given index * @param conversionsToAdd the sequence of conversions to add to conversionsAtIndex */ private static void validateDuplicates(FieldSelector selector, List> conversionsAtIndex, Conversion[] conversionsToAdd) { for (Conversion toAdd : conversionsToAdd) { for (Conversion existing : conversionsAtIndex) { if (toAdd == existing) { throw new DataProcessingException("Duplicate conversion " + toAdd.getClass().getName() + " being applied to " + selector.describe()); } } } } /** * Queries if any conversions were associated with any field * * @return true if no conversions were associated with any field; false otherwise */ public boolean isEmpty() { return conversionsMap == null || conversionsMap.isEmpty(); } public AbstractConversionMapping clone() { try { return (AbstractConversionMapping) super.clone(); } catch (CloneNotSupportedException e) { throw new IllegalStateException(e); } } public AbstractConversionMapping clone(Map clonedSelectors, List clonedConversionSequence) { AbstractConversionMapping out = clone(); out.conversionSequence = clonedConversionSequence; if (conversionsMap != null) { out.conversionsMap = new HashMap[]>(); for (FieldSelector selector : this.conversionSequence) { FieldSelector clone = clonedSelectors.get(selector); if (clone == null) { throw new IllegalStateException("Internal error cloning conversion mappings"); } Conversion[] conversions = conversionsMap.get(selector); out.conversionsMap.put(clone, conversions); } } return out; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy