candr.yoclip.option.OptionPropertiesField.java Source code

Java tutorial

Introduction

Here is the source code for candr.yoclip.option.OptionPropertiesField.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 candr.yoclip.option;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import candr.yoclip.*;
import candr.yoclip.annotation.OptionProperties;
import candr.yoclip.annotation.PropertyDescription;

/**
 * Associate an {@link candr.yoclip.annotation.OptionProperties OptionProperties} annotation with a Java {@code Field}
 * in a class that contains option parser annotations.
 */
class OptionPropertiesField<T> extends AbstractFieldOption<T> implements ParserOption<T> {

    /**
     * The {@link candr.yoclip.annotation.OptionProperties} annotation associated with a field.
     */
    final private OptionProperties optionProperties;

    /**
     * Initializes the field descriptor to create an association between the bean {@code Field} and the
     * {@code OptionProperties} annotation.
     *
     * @param optionProperties The annotation associated with the bean field.
     * @param field            The bean field annotated with {@code OptionProperties}.
     * @throws candr.yoclip.OptionsBadTypeException if the field is not assignable to the Java {@code Map} interface.
     * @throws candr.yoclip.OptionsBadNameException if the {@link candr.yoclip.annotation.OptionProperties#name() name}
     *                                              is empty.
     */
    protected OptionPropertiesField(final OptionProperties optionProperties, final Field field) {

        super(field);

        if (!Map.class.isAssignableFrom(field.getType())) {
            throw new OptionsBadTypeException(field.getName() + " must be a Map");
        }
        if (StringUtils.isEmpty(optionProperties.name())) {
            throw new OptionsBadNameException(field.getName() + " option name is empty.");
        }

        this.optionProperties = optionProperties;
    }

    /**
     * Get the names associated with the option.
     *
     * @return the option names as a string array or an empty array if the option does not have a name associated with
     * it.
     */
    @Override
    public String[] getNames() {

        return new String[] { getOptionProperties().name() };
    }

    /**
     * Gets the {@link candr.yoclip.annotation.OptionProperties#usage() OptionProperties} {@code usage} field. Please
     * refer to the {@link OptionUtils#combine(String[]) combine} method for rules on how the string will be formatted.
     *
     * @return the option usage or empty string if the usage has not been set.
     */
    @Override
    public String getUsage() {

        return OptionUtils.combine(getOptionProperties().usage());
    }

    /**
     * Gets the {@link candr.yoclip.annotation.OptionProperties#description() OptionProperties} {@code description}
     * field. Please refer to the {@link OptionUtils#combine(String[]) combine} method for rules on how the string will be
     * formatted.
     *
     * @return the option description or empty string if the description has not been set.
     */
    @Override
    public String getDescription() {

        return OptionUtils.combine(getOptionProperties().description());
    }

    /**
     * Gets the {@link candr.yoclip.annotation.OptionProperties#required() OptionProperties} {@code required} field.
     *
     * @return {@code true} if the option is required, {@code false} otherwise.
     */
    @Override
    public boolean isRequired() {

        return getOptionProperties().required();
    }

    /**
     * The bean accessor for the option properties annotation.
     *
     * @return the option properties annotation.
     */
    protected OptionProperties getOptionProperties() {
        return optionProperties;
    }

    /**
     * Indicates the field is annotated with a {@link candr.yoclip.annotation.OptionProperties OptionProperties}
     * annotation.
     *
     * @return Always returns {@code true}.
     */
    @Override
    public boolean isProperties() {

        return true;
    }

    @Override
    public List<Pair<String, String>> getPropertyDescriptions() {

        final PropertyDescription[] propertyDescriptions = getOptionProperties().propertyDescriptions();

        if (ArrayUtils.isEmpty(propertyDescriptions)) {
            return Collections.emptyList();
        }

        List<Pair<String, String>> synopsisAndDetails = new LinkedList<Pair<String, String>>();
        for (final PropertyDescription propertyDescription : propertyDescriptions) {
            synopsisAndDetails.add(
                    Pair.of(propertyDescription.synopsis(), OptionUtils.combine(propertyDescription.details())));
        }

        return synopsisAndDetails;
    }

    @Override
    public void setOption(final T bean, final String value) {

        // this should never happen unless the property pattern matcher changes
        final String[] keyAndValue = StringUtils.split(value, OptionProperties.KEY_VALUE_SEPARATOR);
        if (keyAndValue.length != 2) {
            throw new OptionsParseException("Yikes!!! Expected key=value not '" + value + "'...");
        }

        set(bean, new Value<T>() {

            @Override
            public void set(final T bean, final Field field) throws IllegalAccessException {

                try {

                    final Method argumentsSetter = field.getType().getMethod("put", Object.class, Object.class);
                    final Object propertiesMap = field.get(bean);
                    if (null == propertiesMap) {
                        throw new OptionsParseException(field.getName() + " is null and cannot be set...");
                    }
                    argumentsSetter.invoke(propertiesMap, keyAndValue[0], keyAndValue[1]);

                } catch (final NoSuchMethodException e) {
                    throw new OptionsParseException("Yikes!!! Could not get Map.put(key,value) method...", e);

                } catch (final InvocationTargetException e) {
                    throw new OptionsParseException("Yikes!!! Map.put(key,value) threw an exception...", e);
                }
            }
        });
    }
}