package io.eguan.configuration; /* * #%L * Project eguan * %% * Copyright (C) 2012 - 2017 Oodrive * %% * 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. * #L% */ import io.eguan.configuration.ValidationError.ErrorType; import java.util.ArrayList; import java.util.Collection; import java.util.Objects; import java.util.regex.PatternSyntaxException; import com.google.common.base.Strings; /** * Abstract generic class for any multi-valued configuration keys. * * @author oodrive * @author pwehrle * @author llambert * * @param <T> * the {@link Collection} type to support * @param <S> * the {@link Object} type to support as part of the list */ public abstract class MultiValuedConfigKey<T extends Collection<S>, S> extends AbstractConfigKey { private final Class<T> collectionType; private final Class<S> itemType; private final String separator; /** * Internal constructor to be called by subclasses. * * @param name * see {@link AbstractConfigKey#AbstractConfigKey(String)} * @param separator * a separator to use when {@link #parseValue(String) parsing} or {@link #valueToString(Object) * serializing} values. * @param collectionType * the collection type's {@link Class} instance representing {@link T} * @param itemType * the item type's {@link Class} instance representing {@link S} */ protected MultiValuedConfigKey(final String name, final String separator, final Class<T> collectionType, final Class<S> itemType) { super(name); if (Strings.isNullOrEmpty(separator)) { throw new IllegalArgumentException("Separator is null or empty"); } this.separator = separator; this.itemType = Objects.requireNonNull(itemType); this.collectionType = Objects.requireNonNull(collectionType); } @Override public final T getTypedValue(final MetaConfiguration configuration) throws IllegalStateException, ClassCastException, NullPointerException { Objects.requireNonNull(configuration); checkConfigForKey(configuration); return makeDefensiveCopy(collectionType.cast(configuration.getValue(this))); } @Override protected final T parseValue(final String value) throws PatternSyntaxException, IllegalArgumentException, NullPointerException { if (value.isEmpty()) { // Returns the default value or null when there is no default value @SuppressWarnings("unchecked") final T defaultValue = (T) getDefaultValue(); return defaultValue; } final String[] splitValues = value.split(separator); final ArrayList<S> parsedList = new ArrayList<S>(splitValues.length); for (final String keyValue : splitValues) { parsedList.add(getItemValueFromString(keyValue)); } return getCollectionFromValueList(parsedList); } @Override protected final ValidationError checkValue(final Object value) { final ValidationError result = checkForNullAndRequired(value); if (result != ValidationError.NO_ERROR) { return (result.getType() == ValidationError.ErrorType.VALUE_NULL) ? ValidationError.NO_ERROR : result; } try { final T valueCollection = collectionType.cast(value); for (final Object currObject : valueCollection) { itemType.cast(currObject); } return performAdditionalValueChecks(valueCollection); } catch (final ClassCastException ce) { return new ValidationError(ErrorType.VALUE_INVALID, null, this, value, ce.getMessage()); } } /** * Gets the {@link #separator}. * * @return the immutable separator of collection elements for this instance. */ protected final String getSeparator() { return this.separator; } /** * Utility method to provide {@link #parseValue(String)} with the correct {@link T collection type} to return * without resorting to reflection. * * @param values * an {@link ArrayList} of correctly typed values * @return a {@link Collection} of the right {@link T collection type} and with the same content as values to be * returned by {@link #parseValue(String)} * @see T */ protected abstract T getCollectionFromValueList(ArrayList<S> values); /** * Utility method to provide {@link #parseValue(String)} with correctly {@link S typed} items when parsing input * Strings. Again, this is to avoid resorting to reflection. * * @param value * an un-{@link String#trim() trimmed} token of the list to be parsed into a single value * @return the {@link S correctly typed} item * * @see S */ protected abstract S getItemValueFromString(String value); /** * Makes a defensive copy of the original value. * * @param value * the value to copy, may be <code>null</code> * @return an independent copy or <code>null</code> if the argument was <code>null</code> */ protected abstract T makeDefensiveCopy(T value); /** * Performs additional value checks implemented by subclasses on the value already cast to the target type by * {@link #checkValue(Object)}. * * This method is called by {@link #checkValue(Object)} after the collection value and its elements have been * successfully cast to the desired type. * * @param value * the value to check * @return a specific {@link ValidationError} if checks failed, {@link ValidationError#NO_ERROR} otherwise */ protected abstract ValidationError performAdditionalValueChecks(T value); }