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

dev.miku.r2dbc.mysql.OptionMapper Maven / Gradle / Ivy

/*
 * Copyright 2018-2020 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 dev.miku.r2dbc.mysql;

import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.Option;

import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * An utility data parser for {@link Option}.
 *
 * @see MySqlConnectionFactoryProvider using this utility.
 * @since 0.8.2
 */
final class OptionMapper {

    private final ConnectionFactoryOptions options;

    OptionMapper(ConnectionFactoryOptions options) {
        this.options = options;
    }

    SourceSpec from(Option option) {
        return new SourceSpec(options, option);
    }

     void consume(Option option, Consumer consumer) {
        T t = options.getValue(option);

        if (t != null) {
            consumer.accept(t);
        }
    }

     void requiredConsume(Option option, Consumer consumer) {
        consumer.accept(options.getRequiredValue(option));
    }
}

final class SourceSpec {

    private final ConnectionFactoryOptions options;

    private final Option option;

    SourceSpec(ConnectionFactoryOptions options, Option option) {
        this.options = options;
        this.option = option;
    }

     Source asInstance(Class type) {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (type.isInstance(value)) {
            return new Source.Impl<>(type.cast(value));
        } else if (value instanceof String) {
            try {
                Class implementation = Class.forName((String) value);

                if (type.isAssignableFrom(implementation)) {
                    return new Source.Impl<>(type.cast(implementation.getDeclaredConstructor().newInstance()));
                }
                // Otherwise not an implementation, convert failed.
            } catch (ReflectiveOperationException e) {
                throw new IllegalArgumentException("Cannot instantiate '" + value + "'", e);
            }
        }

        throw new IllegalArgumentException(toMessage(option, value, type.getName()));
    }

     Source asInstance(Class type, Function mapping) {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (type.isInstance(value)) {
            return new Source.Impl<>(type.cast(value));
        } else if (value instanceof String) {
            // Type cast for check mapping result.
            return new Source.Impl<>(type.cast(mapping.apply((String) value)));
        }

        throw new IllegalArgumentException(toMessage(option, value, type.getTypeName()));
    }

    Source asStrings() {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (value instanceof String[]) {
            return new Source.Impl<>((String[]) value);
        } else if (value instanceof String) {
            return new Source.Impl<>(((String) value).split(","));
        } else if (value instanceof Collection) {
            return new Source.Impl<>(((Collection) value).stream()
                .map(String.class::cast).toArray(String[]::new));
        }

        throw new IllegalArgumentException(toMessage(option, value, "String[]"));
    }

    Source asBoolean() {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (value instanceof Boolean) {
            return new Source.Impl<>((Boolean) value);
        } else if (value instanceof String) {
            return new Source.Impl<>(Boolean.parseBoolean((String) value));
        }

        throw new IllegalArgumentException(toMessage(option, value, "Boolean"));
    }

    Source asInt() {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (value instanceof Integer) {
            // Reduce the cost of re-boxed.
            return new Source.Impl<>((Integer) value);
        } else if (value instanceof Number) {
            return new Source.Impl<>(((Number) value).intValue());
        } else if (value instanceof String) {
            return new Source.Impl<>(Integer.parseInt((String) value));
        }

        throw new IllegalArgumentException(toMessage(option, value, "Integer"));
    }

    Source asString() {
        Object value = options.getValue(option);

        if (value == null) {
            return Source.nilSource();
        }

        if (value instanceof String) {
            return new Source.Impl<>((String) value);
        }

        throw new IllegalArgumentException(toMessage(option, value, "String"));
    }

    @SuppressWarnings("unchecked")
    void servePrepare(Consumer enables, Consumer> preferred) {
        Object value = options.getValue(option);

        if (value == null) {
            return;
        }

        if (value instanceof Boolean) {
            enables.accept((Boolean) value);
            return;
        } else if (value instanceof Predicate) {
            preferred.accept((Predicate) value);
            return;
        } else if (value instanceof String) {
            String serverPreparing = (String) value;

            if ("true".equalsIgnoreCase(serverPreparing) || "false".equalsIgnoreCase(serverPreparing)) {
                enables.accept(Boolean.parseBoolean(serverPreparing));
                return;
            } else {
                try {
                    Class implementation = Class.forName(serverPreparing);

                    if (Predicate.class.isAssignableFrom(implementation)) {
                        preferred.accept((Predicate) implementation.getDeclaredConstructor().newInstance());
                        return;
                    }
                    // Otherwise not an implementation, convert failed.
                } catch (ReflectiveOperationException e) {
                    throw new IllegalArgumentException("Cannot instantiate '" + value + "'", e);
                }
            }
        }

        throw new IllegalArgumentException(toMessage(option, value, "Boolean or Predicate"));
    }

    private static String toMessage(Option option, Object value, String type) {
        return "Cannot convert value " + value + " of " + value.getClass() + " as " + type + " for option " + option.name();
    }
}

@FunctionalInterface
interface Source {

    Otherwise into(Consumer consumer);

    @SuppressWarnings("unchecked")
    static  Source nilSource() {
        return (Source) Nil.INSTANCE;
    }

    final class Impl implements Source {

        private final T value;

        Impl(T value) {
            this.value = value;
        }

        public Otherwise into(Consumer consumer) {
            consumer.accept(value);
            return Otherwise.NOOP;
        }
    }

    enum Nil implements Source {

        INSTANCE;

        @Override
        public Otherwise into(Consumer consumer) {
            return Otherwise.FALL;
        }
    }
}

@FunctionalInterface
interface Otherwise {

    Otherwise NOOP = ignored -> {
    };

    Otherwise FALL = Runnable::run;

    /**
     * Invoked if the previous {@link Source} outcome did not match.
     *
     * @param runnable the {@link Runnable} that should be invoked.
     */
    void otherwise(Runnable runnable);
}