com.github.rvesse.airline.model.OptionMetadata.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rvesse.airline.model.OptionMetadata.java

Source

/**
 * Copyright (C) 2010-16 the original author or authors.
 *
 * 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.github.rvesse.airline.model;

import com.github.rvesse.airline.Accessor;
import com.github.rvesse.airline.annotations.OptionType;
import com.github.rvesse.airline.restrictions.OptionRestriction;
import com.github.rvesse.airline.utils.AirlineUtils;
import com.github.rvesse.airline.utils.predicates.restrictions.IsRequiredOptionFinder;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.SetUtils;

public class OptionMetadata {
    private final OptionType optionType;
    private final Set<String> options;
    private final String title, description;
    private final int arity;
    private final boolean hidden, overrides, sealed;
    private final List<OptionRestriction> restrictions;
    private Set<Accessor> accessors;

    //@formatter:off
    public OptionMetadata(OptionType optionType, Iterable<String> options, String title, String description,
            int arity, boolean hidden, boolean overrides, boolean sealed, Iterable<OptionRestriction> restrictions,
            Iterable<Field> path) {
        //@formatter:on
        if (optionType == null)
            throw new NullPointerException("optionType cannot be null");
        if (options == null)
            throw new NullPointerException("options cannot be null");
        if (!options.iterator().hasNext())
            throw new NullPointerException("options cannot be empty");
        if (title == null)
            throw new NullPointerException("title cannot be null");

        this.optionType = optionType;
        this.options = AirlineUtils.unmodifiableSetCopy(options);
        this.title = title;
        this.description = description;
        this.arity = arity;
        this.hidden = hidden;
        this.overrides = overrides;
        this.sealed = sealed;
        this.restrictions = restrictions != null ? AirlineUtils.unmodifiableListCopy(restrictions)
                : Collections.<OptionRestriction>emptyList();

        if (path != null) {
            this.accessors = SetUtils.unmodifiableSet(AirlineUtils.singletonSet(new Accessor(path)));
        }
    }

    public OptionMetadata(Iterable<OptionMetadata> options) {
        if (options == null)
            throw new NullPointerException("options cannot be null");
        if (!options.iterator().hasNext())
            throw new IllegalArgumentException("options cannot be empty");

        OptionMetadata option = options.iterator().next();

        this.optionType = option.optionType;
        this.options = option.options;
        this.title = option.title;
        this.description = option.description;
        this.arity = option.arity;
        this.hidden = option.hidden;
        this.overrides = option.overrides;
        this.sealed = option.sealed;
        this.restrictions = option.restrictions;

        Set<Accessor> accessors = new LinkedHashSet<Accessor>();
        for (OptionMetadata other : options) {
            if (!option.equals(other))
                throw new IllegalArgumentException(
                        String.format("Duplicate options definitions: %s, %s", option, other));

            accessors.addAll(other.getAccessors());
        }
        this.accessors = SetUtils.unmodifiableSet(accessors);
    }

    public OptionType getOptionType() {
        return optionType;
    }

    public Set<String> getOptions() {
        return options;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }

    public int getArity() {
        return arity;
    }

    public boolean isRequired() {
        return CollectionUtils.exists(this.restrictions, new IsRequiredOptionFinder());
    }

    public boolean isHidden() {
        return hidden;
    }

    public boolean isOverride() {
        return overrides;
    }

    public boolean isSealed() {
        return sealed;
    }

    public boolean isMultiValued() {
        return accessors.iterator().next().isMultiValued();
    }

    public Class<?> getJavaType() {
        return accessors.iterator().next().getJavaType();
    }

    public Set<Accessor> getAccessors() {
        if (accessors == null) {
            throw new NullPointerException("No accessors defined for option");
        }
        return accessors;
    }

    public List<OptionRestriction> getRestrictions() {
        return this.restrictions;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        OptionMetadata that = (OptionMetadata) o;

        if (arity != that.arity) {
            return false;
        }
        if (hidden != that.hidden) {
            return false;
        }
        if (overrides != that.overrides) {
            return false;
        }
        if (sealed != that.sealed) {
            return false;
        }
        if (description != null ? !description.equals(that.description) : that.description != null) {
            return false;
        }
        if (optionType != that.optionType) {
            return false;
        }
        if (!options.equals(that.options)) {
            return false;
        }
        if (!title.equals(that.title)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = optionType.hashCode();
        result = 31 * result + options.hashCode();
        result = 31 * result + title.hashCode();
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + arity;
        result = 31 * result + (hidden ? 1 : 0);
        result = 31 * result + (overrides ? 1 : 0);
        result = 31 * result + (sealed ? 1 : 0);
        return result;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("OptionMetadata");
        sb.append("{optionType=").append(optionType);
        sb.append(", options=").append(options);
        sb.append(", title='").append(title).append('\'');
        sb.append(", description='").append(description).append('\'');
        sb.append(", arity=").append(arity);
        sb.append(", hidden=").append(hidden);
        sb.append(", override=").append(overrides);
        sb.append(", sealed=").append(sealed);
        sb.append(", accessors=").append(accessors);
        sb.append('}');
        return sb.toString();
    }

    /**
     * Tries to merge the option metadata together such that the child metadata
     * takes precedence. Not all options can be successfully overridden and an
     * error may be thrown in cases where merging is not possible
     * <p>
     * The following pieces of metadata may be overridden:
     * </p>
     * <ul>
     * <li>Title</li>
     * <li>Description</li>
     * <li>Required</li>
     * <li>Hidden</li>
     * </ul>
     * 
     * @param parent
     *            Parent
     * @param child
     *            Child
     * @return Merged metadata
     */
    public static OptionMetadata override(Set<String> names, OptionMetadata parent, OptionMetadata child) {
        // Cannot change option type, arity or names
        if (parent.optionType != child.optionType)
            throw new IllegalArgumentException(
                    String.format("Cannot change optionType when overriding option %s", names));
        if (parent.arity != child.arity)
            throw new IllegalArgumentException(
                    String.format("Cannot change arity when overriding option %s", names));
        if (!parent.options.equals(child.options))
            throw new IllegalArgumentException(
                    String.format("Cannot change option names when overriding option %s", names));

        // Also cannot change the type of the option unless the change is a
        // narrowing conversion
        Class<?> parentType = parent.getJavaType();
        Class<?> childType = child.getJavaType();
        if (!parentType.equals(childType)) {
            if (!parentType.isAssignableFrom(childType)) {
                if (childType.isAssignableFrom(parentType)) {
                    // A widening conversion exists but this is illegal however
                    // we can give a slightly more informative error in this
                    // case
                    throw new IllegalArgumentException(String.format(
                            "Cannot change the Java type from %s to %s when overriding option %s as this is a widening type change - only narrowing type changes are permitted",
                            parentType, childType, names));
                } else {
                    // No conversion exists
                    throw new IllegalArgumentException(String.format(
                            "Cannot change the Java type from %s to %s when overriding option %s - only narrowing type changes where a valid cast exists are permitted",
                            parentType, childType, names));
                }
            }
        }

        // Check for duplicates
        boolean isDuplicate = parent == child || parent.equals(child);

        // Parent must not state it is sealed UNLESS it is a duplicate which can
        // happen when using @Inject to inject options via delegates
        if (parent.sealed && !isDuplicate)
            throw new IllegalArgumentException(
                    String.format("Cannot override option %s as parent option declares it to be sealed", names));

        // Child must explicitly state that it overrides otherwise we cannot
        // override UNLESS it is the case that this is a duplicate which
        // can happen when using @Inject to inject options via delegates
        if (!child.overrides && !isDuplicate)
            throw new IllegalArgumentException(
                    String.format("Cannot override option %s unless child option sets overrides to true", names));

        OptionMetadata merged;
        //@formatter:off
        merged = new OptionMetadata(child.optionType, child.options,
                child.title != null ? child.title : parent.title,
                child.description != null ? child.description : parent.description, child.arity, child.hidden,
                child.overrides, child.sealed,
                child.restrictions.size() > 0 ? child.restrictions : parent.restrictions, null);
        //@formatter:on

        // Combine both child and parent accessors - this is necessary so the
        // parsed value propagates to all classes in the hierarchy
        Set<Accessor> accessors = new LinkedHashSet<>(child.accessors);
        accessors.addAll(parent.accessors);
        merged.accessors = AirlineUtils.unmodifiableSetCopy(accessors);
        return merged;
    }
}