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

org.xnio.Option Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source
 *
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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 org.xnio;

import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.xnio._private.Messages.msg;

/**
 * A strongly-typed option to configure an aspect of a service or connection.  Options are immutable and use identity comparisons
 * and hash codes.  Options should always be declared as public static final members in order to support serialization.
 *
 * @param  the option value type
 */
public abstract class Option implements Serializable {

    private static final long serialVersionUID = -1564427329140182760L;

    private final Class declClass;
    private final String name;

    Option(final Class declClass, final String name) {
        if (declClass == null) {
            throw msg.nullParameter("declClass");
        }
        if (name == null) {
            throw msg.nullParameter("name");
        }
        this.declClass = declClass;
        this.name = name;
    }

    /**
     * Create an option with a simple type.  The class object given must represent some immutable type, otherwise
     * unexpected behavior may result.
     *
     * @param declClass the declaring class of the option
     * @param name the (field) name of this option
     * @param type the class of the value associated with this option
     * @return the option instance
     */
    public static  Option simple(final Class declClass, final String name, final Class type) {
        return new SingleOption(declClass, name, type);
    }

    /**
     * Create an option with a sequence type.  The class object given must represent some immutable type, otherwise
     * unexpected behavior may result.
     *
     * @param declClass the declaring class of the option
     * @param name the (field) name of this option
     * @param elementType the class of the sequence element value associated with this option
     * @return the option instance
     */
    public static  Option> sequence(final Class declClass, final String name, final Class elementType) {
        return new SequenceOption(declClass, name, elementType);
    }

    /**
     * Create an option with a class type.  The class object given may represent any type.
     *
     * @param declClass the declaring class of the option
     * @param name the (field) name of this option
     * @param declType the class object for the type of the class object given
     * @param  the type of the class object given
     * @return the option instance
     */
    public static  Option> type(final Class declClass, final String name, final Class declType) {
        return new TypeOption(declClass, name, declType);
    }

    /**
     * Create an option with a sequence-of-types type.  The class object given may represent any type.
     *
     * @param declClass the declaring class of the option
     * @param name the (field) name of this option
     * @param elementDeclType the class object for the type of the sequence element class object given
     * @param  the type of the sequence element class object given
     * @return the option instance
     */
    public static  Option>> typeSequence(final Class declClass, final String name, final Class elementDeclType) {
        return new TypeSequenceOption(declClass, name, elementDeclType);
    }

    /**
     * Get the name of this option.
     *
     * @return the option name
     */
    public String getName() {
        return name;
    }

    /**
     * Get a human-readable string representation of this object.
     *
     * @return the string representation
     */
    public String toString() {
        return declClass.getName() + "." + name;
    }

    /**
     * Get an option from a string name, using the given classloader.  If the classloader is {@code null}, the bootstrap
     * classloader will be used.
     *
     * @param name the option string
     * @param classLoader the class loader
     * @return the option
     * @throws IllegalArgumentException if the given option name is not valid
     */
    public static Option fromString(String name, ClassLoader classLoader) throws IllegalArgumentException {
        final int lastDot = name.lastIndexOf('.');
        if (lastDot == -1) {
            throw msg.invalidOptionName(name);
        }
        final String fieldName = name.substring(lastDot + 1);
        final String className = name.substring(0, lastDot);
        final Class clazz;
        try {
            clazz = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException e) {
            throw msg.optionClassNotFound(className, classLoader);
        }
        final Field field;
        try {
            field = clazz.getField(fieldName);
        } catch (NoSuchFieldException e) {
            throw msg.noField(fieldName, clazz);
        }
        final int modifiers = field.getModifiers();
        if (! Modifier.isPublic(modifiers)) {
            throw msg.fieldNotAccessible(fieldName, clazz);
        }
        if (! Modifier.isStatic(modifiers)) {
            throw msg.fieldNotStatic(fieldName, clazz);
        }
        final Option option;
        try {
            option = (Option) field.get(null);
        } catch (IllegalAccessException e) {
            throw msg.fieldNotAccessible(fieldName, clazz);
        }
        if (option == null) {
            throw msg.invalidNullOption(name);
        }
        return option;
    }

    /**
     * Return the given object as the type of this option.  If the cast could not be completed, an exception is thrown.
     *
     * @param o the object to cast
     * @return the cast object
     * @throws ClassCastException if the object is not of a compatible type
     */
    public abstract T cast(Object o) throws ClassCastException;

    /**
     * Return the given object as the type of this option.  If the cast could not be completed, an exception is thrown.
     *
     * @param o the object to cast
     * @param defaultVal the value to return if {@code o} is {@code null}
     *
     * @return the cast object
     *
     * @throws ClassCastException if the object is not of a compatible type
     */
    public final T cast(Object o, T defaultVal) throws ClassCastException {
        return o == null ? defaultVal : cast(o);
    }

    /**
     * Parse a string value for this option.
     *
     * @param string the string
     * @param classLoader the class loader to use to parse the value
     * @return the parsed value
     * @throws IllegalArgumentException if the argument could not be parsed
     */
    public abstract T parseValue(String string, ClassLoader classLoader) throws IllegalArgumentException;

    /**
     * Resolve this instance for serialization.
     *
     * @return the resolved object
     * @throws java.io.ObjectStreamException if the object could not be resolved
     */
    protected final Object readResolve() throws ObjectStreamException {
        try {
            final Field field = declClass.getField(name);
            final int modifiers = field.getModifiers();
            if (! Modifier.isPublic(modifiers)) {
                throw new InvalidObjectException("Invalid Option instance (the field is not public)");
            }
            if (! Modifier.isStatic(modifiers)) {
                throw new InvalidObjectException("Invalid Option instance (the field is not static)");
            }
            final Option option = (Option) field.get(null);
            if (option == null) {
                throw new InvalidObjectException("Invalid null Option");
            }
            return option;
        } catch (NoSuchFieldException e) {
            throw new InvalidObjectException("Invalid Option instance (no matching field)");
        } catch (IllegalAccessException e) {
            throw new InvalidObjectException("Invalid Option instance (Illegal access on field get)");
        }
    }

    /**
     * Create a builder for an immutable option set.
     *
     * @return the builder
     */
    public static Option.SetBuilder setBuilder() {
        return new Option.SetBuilder();
    }

    /**
     * A builder for an immutable option set.
     */
    public static class SetBuilder {
        private List> optionSet = new ArrayList>();

        SetBuilder() {
        }

        /**
         * Add an option to this set.
         *
         * @param option the option to add
         * @return this builder
         */
        public Option.SetBuilder add(Option option) {
            if (option == null) {
                throw msg.nullParameter("option");
            }
            optionSet.add(option);
            return this;
        }

        /**
         * Add options to this set.
         *
         * @param option1 the first option to add
         * @param option2 the second option to add
         * @return this builder
         */
        public Option.SetBuilder add(Option option1, Option option2) {
            if (option1 == null) {
                throw msg.nullParameter("option1");
            }
            if (option2 == null) {
                throw msg.nullParameter("option2");
            }
            optionSet.add(option1);
            optionSet.add(option2);
            return this;
        }

        /**
         * Add options to this set.
         *
         * @param option1 the first option to add
         * @param option2 the second option to add
         * @param option3 the third option to add
         * @return this builder
         */
        public Option.SetBuilder add(Option option1, Option option2, Option option3) {
            if (option1 == null) {
                throw msg.nullParameter("option1");
            }
            if (option2 == null) {
                throw msg.nullParameter("option2");
            }
            if (option3 == null) {
                throw msg.nullParameter("option3");
            }
            optionSet.add(option1);
            optionSet.add(option2);
            optionSet.add(option3);
            return this;
        }

        /**
         * Add options to this set.
         *
         * @param options the options to add
         * @return this builder
         */
        public Option.SetBuilder add(Option... options) {
            if (options == null) {
                throw msg.nullParameter("options");
            }
            for (Option option : options) {
                add(option);
            }
            return this;
        }

        /**
         * Add all options from a collection to this set.
         *
         * @param options the options to add
         * @return this builder
         */
        public Option.SetBuilder addAll(Collection> options) {
            if (options == null) {
                throw msg.nullParameter("option");
            }
            for (Option option : options) {
                add(option);
            }
            return this;
        }

        /**
         * Create the immutable option set instance.
         *
         * @return the option set
         */
        public Set> create() {
            return Collections.unmodifiableSet(new LinkedHashSet>(optionSet));
        }
    }

    interface ValueParser {
        T parseValue(String string, ClassLoader classLoader) throws IllegalArgumentException;
    }

    private static final Map, Option.ValueParser> parsers;

    private static final Option.ValueParser noParser = new Option.ValueParser() {
        public Object parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
            throw msg.noOptionParser();
        }
    };

    static {
        final Map, Option.ValueParser> map = new HashMap, Option.ValueParser>();
        map.put(Byte.class, new Option.ValueParser() {
            public Byte parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return Byte.decode(string.trim());
            }
        });
        map.put(Short.class, new Option.ValueParser() {
            public Short parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return Short.decode(string.trim());
            }
        });
        map.put(Integer.class, new Option.ValueParser() {
            public Integer parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return Integer.decode(string.trim());
            }
        });
        map.put(Long.class, new Option.ValueParser() {
            public Long parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return Long.decode(string.trim());
            }
        });
        map.put(String.class, new Option.ValueParser() {
            public String parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return string.trim();
            }
        });
        map.put(Boolean.class, new Option.ValueParser() {
            public Boolean parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return Boolean.valueOf(string.trim());
            }
        });
        map.put(Property.class, new Option.ValueParser() {
            public Object parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                final int idx = string.indexOf('=');
                if (idx == -1) {
                    throw msg.invalidOptionPropertyFormat(string);
                }
                return Property.of(string.substring(0, idx), string.substring(idx + 1, string.length()));
            }
        });
        parsers = map;
    }

    static  Option.ValueParser> getClassParser(final Class argType) {
        return new ValueParser>() {
            public Class parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                try {
                    return Class.forName(string, false, classLoader).asSubclass(argType);
                } catch (ClassNotFoundException e) {
                    throw msg.classNotFound(string, e);
                } catch (ClassCastException e) {
                    throw msg.classNotInstance(string, argType);
                }
            }
        };
    }

    static > Option.ValueParser getEnumParser(final Class enumType) {
        return new ValueParser() {
            public T parseValue(final String string, final ClassLoader classLoader) throws IllegalArgumentException {
                return enumType.cast(Enum.valueOf(asEnum(enumType), string.trim()));
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static > Class asEnum(final Class enumType) {
        return (Class) enumType;
    }

    @SuppressWarnings("unchecked")
    static  Option.ValueParser getParser(final Class argType) {
        if (argType.isEnum()) {
            return getEnumParser(argType);
        } else {
            final Option.ValueParser value = parsers.get(argType);
            return (Option.ValueParser) (value == null ? noParser : value);
        }
    }
}