001/**
002 *  Copyright (C) 2016 Gary Gregory. All rights reserved.
003 *
004 *  See the NOTICE.txt file distributed with this work for additional
005 *  information regarding copyright ownership.
006 *  
007 *  Licensed under the Apache License, Version 2.0 (the "License");
008 *  you may not use this file except in compliance with the License.
009 *  You may obtain a copy of the License at
010 *  
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *  
013 *  Unless required by applicable law or agreed to in writing, software
014 *  distributed under the License is distributed on an "AS IS" BASIS,
015 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 *  See the License for the specific language governing permissions and
017 *  limitations under the License.
018 */
019package com.garygregory.jcommander.converters;
020
021import java.net.URI;
022import java.util.Objects;
023
024import com.beust.jcommander.ParameterException;
025import com.beust.jcommander.converters.BaseConverter;
026import com.beust.jcommander.converters.IntegerConverter;
027import com.garygregory.jcommander.converters.net.URIConverter;
028
029/**
030 * Provides common services for converters in this package
031 * 
032 * @param <T>
033 *            the target type of the conversion.
034 * @since 1.0.0
035 * @author <a href="mailto:ggregory@garygregory.com">Gary Gregory</a>
036 */
037public abstract class AbstractBaseConverter<T> extends BaseConverter<T> {
038
039    /**
040     * The target class to convert strings.
041     */
042    protected final Class<T> targetClass;
043
044    /**
045     * Whether or not a null conversion failure throws a ParameterException.
046     */
047    protected final boolean failOnNull;
048
049    /**
050     * Constructs a new instance for the given option and target class.
051     * 
052     * @param optionName
053     *            The option name, may be null.
054     * @param targetClass
055     *            must not be null
056     */
057    public AbstractBaseConverter(final String optionName, final Class<T> targetClass) {
058        this(optionName, targetClass, true);
059    }
060
061    /**
062     * Constructs a new instance for the given option, target class and fail-on-null.
063     * 
064     * @param optionName
065     *            may be null
066     * @param targetClass
067     *            must not be null
068     * @param failOnNull
069     *            if true, the converter fails when the conversion results in a null value
070     */
071    public AbstractBaseConverter(final String optionName, final Class<T> targetClass, final boolean failOnNull) {
072        super(optionName);
073        this.failOnNull = failOnNull;
074        this.targetClass = Objects.requireNonNull(targetClass, "targetClass for " + getClass());
075    }
076
077    /**
078     * Converts the given value or throws a {@link ParameterException}. Delegates the actual conversion to subclasses with
079     * {@link #convertImpl(String)}.
080     * 
081     * @param value
082     *            the value to convert.
083     * @throws ParameterException
084     *             for a conversion problem
085     */
086    @Override
087    public T convert(final String value) {
088        try {
089            final T result = convertImpl(value);
090            if (result == null && failOnNull) {
091                throw newParameterException(value);
092            }
093            return result;
094        } catch (final Exception e) {
095            throw newParameterException(value, e);
096        }
097    }
098
099    /**
100     * Converts a value.
101     * 
102     * @param value
103     *            the value to convert
104     * @return the converted value
105     * @throws Exception
106     *             subclasses can throw any Exception
107     */
108    protected abstract T convertImpl(String value) throws Exception;
109
110    /**
111     * Builds an error message for the given value in error
112     * 
113     * @param value
114     *            the value that cause the error
115     * @return an error message
116     */
117    protected String getErrorString(final String value) {
118        return getClass().getName() + " could not convert \"" + value + "\" to an instance of " + targetClass + " for option "
119                + getOptionName();
120    }
121
122    /**
123     * Returns true if the array is of size 1, false otherwise.
124     * 
125     * @param split
126     *            an array
127     * @return true if the array is of size 1, false otherwise.
128     */
129    protected boolean isSingle(final String[] split) {
130        return split.length == 1;
131    }
132
133    /**
134     * Creates a new {@link ParameterException} for the given value.
135     * 
136     * @param value
137     *            the value in error
138     * @return a new ParameterException
139     */
140    protected ParameterException newParameterException(final String value) {
141        return new ParameterException(getErrorString(value));
142    }
143
144    /**
145     * Creates a new {@link ParameterException} for the given value and throwable
146     * 
147     * @param value
148     *            the value in error
149     * @param t
150     *            the throwable
151     * @return a new ParameterException
152     */
153    protected ParameterException newParameterException(final String value, final Throwable t) {
154        return new ParameterException(getErrorString(value), t);
155    }
156
157    /**
158     * Splits the given string using {@link ConverterConstants#VALUE_SEPARATOR} as the boundary.
159     * 
160     * @param value
161     *            the string to split
162     * @return the split array result
163     */
164    protected String[] split(final String value) {
165        return value.split(ConverterConstants.VALUE_SEPARATOR);
166    }
167
168    /**
169     * Converts the given value to an int for the given option.
170     * 
171     * @param optionName
172     *            the option name
173     * @param value
174     *            the value to parse
175     * @return the int result
176     */
177    protected int toInt(final String optionName, final String value) {
178        return new IntegerConverter(optionName).convert(value).intValue();
179    }
180
181    /**
182     * Converts the given string into a URI
183     * 
184     * @param optionName
185     *            the option name
186     * @param value
187     *            a string
188     * 
189     * @return a new URI
190     * @see java.net.URI
191     */
192    protected URI toURI(final String optionName, final String value) {
193        return new URIConverter(null).convert(value);
194    }
195
196}