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

org.neo4j.helpers.Settings Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2015 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.helpers;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Pattern;

import org.neo4j.graphdb.config.InvalidSettingException;
import org.neo4j.graphdb.config.Setting;
import org.neo4j.helpers.collection.Iterables;
import org.neo4j.io.fs.FileUtils;
import org.neo4j.kernel.configuration.Config;

/**
 * Create settings for configurations in Neo4j. See {@link org.neo4j.graphdb.factory.GraphDatabaseSettings} for example.
 *
 * Each setting has a name, a parser that converts a string to the type of the setting, a default value,
 * an optional inherited setting, and optional value converters.
 *
 * A parser is a function that takes a string and converts to some type T. The parser may throw IllegalArgumentException
 * if it fails.
 *
 * The default value is the string representation of what you want as default. Special values are the constants NO_DEFAULT,
 * which means that you don't want any default value at all, and MANDATORY, which means that the user has to specify a value
 * for this setting. Not providing a mandatory value for a setting leads to an IllegalArgumentException.
 *
 * If a setting does not have a provided value, and no default, then
 */
public final class Settings
{
    private static final String MATCHES_PATTERN_MESSAGE = "matches the pattern `%s`";

    private interface SettingHelper
            extends Setting
    {
        String lookup( Function settings );

        String defaultLookup( Function settings );
    }

    // Set default value to this if user HAS to set a value
    @SuppressWarnings("RedundantStringConstructorCall")
    // It's an explicitly allocated string so identity equality checks work
    public static final String MANDATORY = new String( "mandatory" );
    public static final String NO_DEFAULT = null;
    public static final String EMPTY = "";

    public static final String TRUE = "true";
    public static final String FALSE = "false";

    public static final String DEFAULT = "default";

    public static final String SEPARATOR = ",";

    public static final String DURATION_FORMAT = "\\d+(ms|s|m)";
    public static final String SIZE_FORMAT = "\\d+[kmgKMG]?";

    private static final String DURATION_UNITS = DURATION_FORMAT.substring(
            DURATION_FORMAT.indexOf( '(' ) + 1, DURATION_FORMAT.indexOf( ')' ) )
            .replace( "|", ", " );
    private static final String SIZE_UNITS = Arrays.toString(
            SIZE_FORMAT.substring( SIZE_FORMAT.indexOf( '[' ) + 1,
                    SIZE_FORMAT.indexOf( ']' ) )
                    .toCharArray() )
            .replace( "[", "" )
            .replace( "]", "" );

    public static final String ANY = ".+";

    @SuppressWarnings("unchecked")
    public static  Setting setting( final String name, final Function parser,
                                          final String defaultValue )
    {
        return setting( name, parser, defaultValue, (Setting) null );
    }

    public static  Setting setting( final String name, final Function parser,
                                          final String defaultValue,
                                          final Function2, T>... valueConverters )
    {
        return setting( name, parser, defaultValue, null, valueConverters );
    }

    @SuppressWarnings("unchecked")
    public static  Setting setting( final String name, final Function parser,
                                          final Setting inheritedSetting )
    {
        return setting( name, parser, null, inheritedSetting );
    }

    public static  Setting setting( final String name, final Function parser,
                                          final String defaultValue,
                                          final Setting inheritedSetting, final Function2, T>... valueConverters )
    {
        Function, String> valueLookup = named( name );

        Function, String> defaultLookup;
        if ( defaultValue != null )
        {
            // This is explicitly an identity comparison. We are comparing against the known instance above,
            // using String.equals() here would mean that we could not have settings that have the string "mandatory"
            // as its default value.
            //noinspection StringEquality
            if ( defaultValue == MANDATORY ) // yes, this really is supposed to be ==
            {
                defaultLookup = mandatory( valueLookup );
            }
            else
            {
                defaultLookup = withDefault( defaultValue, valueLookup );
            }
        }
        else
        {
            defaultLookup = Functions.nullFunction();
        }

        if ( inheritedSetting != null )
        {
            valueLookup = inheritedValue( valueLookup, inheritedSetting );
            defaultLookup = inheritedDefault( defaultLookup, inheritedSetting );
        }

        return new DefaultSetting( name, parser, valueLookup, defaultLookup, valueConverters );
    }

    private static  Function, String> inheritedValue( final Function, String> lookup, final Setting inheritedSetting )
    {
        return new Function, String>()
        {
            @Override
            public String apply( Function settings )
            {
                String value = lookup.apply( settings );
                if ( value == null )
                {
                    value = ((SettingHelper) inheritedSetting).lookup( settings );
                }
                return value;
            }
        };
    }

    private static  Function, String> inheritedDefault( final Function, String> lookup, final Setting inheritedSetting )
    {
        return new Function, String>()
        {
            @Override
            public String apply( Function settings )
            {
                String value = lookup.apply( settings );
                if ( value == null )
                {
                    value = ((SettingHelper) inheritedSetting).defaultLookup( settings );
                }
                return value;
            }
        };
    }

    public static final Function INTEGER = new Function()
    {
        @Override
        public Integer apply( String value )
        {
            try
            {
                return Integer.parseInt( value );
            }
            catch ( NumberFormatException e )
            {
                throw new IllegalArgumentException( "not a valid integer value" );
            }
        }

        @Override
        public String toString()
        {
            return "an integer";
        }
    };

    public static final Function LONG = new Function()
    {
        @Override
        public Long apply( String value )
        {
            try
            {
                return Long.parseLong( value );
            }
            catch ( NumberFormatException e )
            {
                throw new IllegalArgumentException( "not a valid long value" );
            }
        }

        @Override
        public String toString()
        {
            return "a long";
        }
    };

    public static final Function BOOLEAN = new Function()
    {
        @Override
        public Boolean apply( String value )
        {
            if ( value.equalsIgnoreCase( "true" ) )
            {
                return true;
            }
            else if ( value.equalsIgnoreCase( "false" ) )
            {
                return false;
            }
            else
            {
                throw new IllegalArgumentException( "must be 'true' or 'false'" );
            }
        }

        @Override
        public String toString()
        {
            return "a boolean";
        }
    };

    public static final Function FLOAT = new Function()
    {
        @Override
        public Float apply( String value )
        {
            try
            {
                return Float.parseFloat( value );
            }
            catch ( NumberFormatException e )
            {
                throw new IllegalArgumentException( "not a valid float value" );
            }
        }

        @Override
        public String toString()
        {
            return "a float";
        }
    };

    public static final Function DOUBLE = new Function()
    {
        @Override
        public Double apply( String value )
        {
            try
            {
                return Double.parseDouble( value );
            }
            catch ( NumberFormatException e )
            {
                throw new IllegalArgumentException( "not a valid double value" );
            }
        }

        @Override
        public String toString()
        {
            return "a double";
        }
    };

    public static final Function STRING = new Function()
    {
        @Override
        public String apply( String value )
        {
            return value.trim();
        }

        @Override
        public String toString()
        {
            return "a string";
        }
    };

    public static final Function> STRING_LIST = new Function>()
    {
        @Override
        public List apply( String value )
        {
            String[] list = value.split( SEPARATOR );
            List result = new ArrayList();
            for( String item : list)
            {
                item = item.trim();
                if( !item.equals( "" ) )
                    result.add( item );
            }
            return result;
        }

        @Override
        public String toString()
        {
            return "a comma-seperated string";
        }
    };

    public static final Function HOSTNAME_PORT = new Function()
    {
        @Override
        public HostnamePort apply( String value )
        {
            return new HostnamePort( value );
        }

        @Override
        public String toString()
        {
            return "a hostname and port";
        }
    };

    public static final Function DURATION = new Function()
    {
        @Override
        public Long apply( String value )
        {
            return TimeUtil.parseTimeMillis.apply( value );
        }

        @Override
        public String toString()
        {
            return "a duration (valid units are `" + DURATION_UNITS.replace( ", ", "`, `" ) + "`)";
        }
    };

    public static final Function BYTES = new Function()
    {
        @Override
        public Long apply( String value )
        {
            try
            {
                String mem = value.toLowerCase();
                long multiplier = 1;
                if ( mem.endsWith( "k" ) )
                {
                    multiplier = 1024;
                    mem = mem.substring( 0, mem.length() - 1 );
                }
                else if ( mem.endsWith( "m" ) )
                {
                    multiplier = 1024 * 1024;
                    mem = mem.substring( 0, mem.length() - 1 );
                }
                else if ( mem.endsWith( "g" ) )
                {
                    multiplier = 1024 * 1024 * 1024;
                    mem = mem.substring( 0, mem.length() - 1 );
                }

                long bytes = Long.parseLong( mem.trim() ) * multiplier;
                if ( bytes < 0 )
                {
                    throw new IllegalArgumentException(
                            value + " is not a valid number of bytes. Must be positive or zero." );
                }
                return bytes;
            }
            catch ( NumberFormatException e )
            {
                throw new IllegalArgumentException( String.format( "%s is not a valid size, must be e.g. 10, 5K, 1M, " +
                        "11G", value ) );
            }
        }

        @Override
        public String toString()
        {
            return "a byte size (valid multipliers are `" + SIZE_UNITS.replace( ", ", "`, `" ) + "`)";
        }
    };

    public static final Function URI =
            new Function()
            {
                @Override
                public URI apply( String value )
                {
                    try
                    {
                        return new URI( value );
                    }
                    catch ( URISyntaxException e )
                    {
                        throw new IllegalArgumentException( "not a valid URI" );
                    }
                }

                @Override
                public String toString()
                {
                    return "a URI";
                }
            };

    public static final Function NORMALIZED_RELATIVE_URI = new Function()
    {
        @Override
        public URI apply( String value )
        {
            try
            {
                String normalizedUri = new URI( value ).normalize().getPath();
                if ( normalizedUri.endsWith( "/" ) )
                {
                    // Force the string end without "/"
                    normalizedUri = normalizedUri.substring( 0, normalizedUri.length() - 1 );
                }
                return new URI( normalizedUri );
            }
            catch ( URISyntaxException e )
            {
                throw new IllegalArgumentException( "not a valid URI" );
            }
        }

        @Override
        public String toString()
        {
            return "a URI";
        }
    };

    public static final Function PATH = new Function()
    {
        @Override
        public File apply( String setting )
        {
            setting = FileUtils.fixSeparatorsInPath( setting );

            return new File( setting );
        }

        @Override
        public String toString()
        {
            return "a path";
        }
    };

    /**
     * For values expressed with a unit such as {@code 100M}.
     * 
     * 
    *
  • 100M
    ==> 100 * 1024 * 1024
  • *
  • 37261
    ==> 37261
  • *
  • 2g
    ==> 2 * 1024 * 1024 * 1024
  • *
  • 50m
    ==> 50 * 1024 * 1024
  • *
  • 10k
    ==> 10 * 1024
  • *
*/ public static final Function LONG_WITH_OPTIONAL_UNIT = new Function() { @Override public Long apply( String from ) { return Config.parseLongWithUnit( from ); } }; public static Function options( final Class enumClass ) { return options( EnumSet.allOf( enumClass ) ); } public static Function options( T... optionValues ) { return Settings.options( Iterables.iterable( optionValues ) ); } public static Function options( final Iterable optionValues ) { return new Function() { @Override public T apply( String value ) { for ( T optionValue : optionValues ) { if ( optionValue.toString().equals( value ) ) { return optionValue; } } throw new IllegalArgumentException( "must be one of " + Iterables.toList( optionValues ).toString() ); } @Override public String toString() { StringBuilder builder = new StringBuilder( ); builder.append( "one of `" ); String comma = ""; for ( T optionValue : optionValues ) { builder.append( comma ).append( optionValue.toString() ); comma = "`, `"; } builder.append( "`" ); return builder.toString(); } }; } public static Function> list( final String separator, final Function itemParser ) { return new Function>() { @Override public List apply( String value ) { List list = new ArrayList(); if ( value.length() > 0 ) { String[] parts = value.split( separator ); for ( String part : parts ) { list.add( itemParser.apply( part ) ); } } return list; } @Override public String toString() { return "a list separated by \"" + separator + "\" where items are " + itemParser.toString(); } }; } // Modifiers public static Function2, String> matches( final String regex ) { final Pattern pattern = Pattern.compile( regex ); return new Function2, String>() { @Override public String apply( String value, Function settings ) { if ( !pattern.matcher( value ).matches() ) { throw new IllegalArgumentException( "value does not match expression:" + regex ); } return value; } @Override public String toString() { return String.format( MATCHES_PATTERN_MESSAGE, regex ); } }; } public static > Function2, T> min( final T min ) { return new Function2, T>() { @Override public T apply( T value, Function settings ) { if ( value != null && value.compareTo( min ) < 0 ) { throw new IllegalArgumentException( String.format( "minimum allowed value is: %s", min ) ); } return value; } @Override public String toString() { return "is minimum `" + min + "`"; } }; } public static > Function2, T> max( final T max ) { return new Function2, T>() { @Override public T apply( T value, Function settings ) { if ( value != null && value.compareTo( max ) > 0 ) { throw new IllegalArgumentException( String.format( "maximum allowed value is: %s", max ) ); } return value; } @Override public String toString() { return "is maximum `" + max + "`"; } }; } public static > Function2, T> range( final T min, final T max ) { return new Function2, T>() { @Override public T apply( T from1, Function from2 ) { return min(min).apply( max(max).apply( from1, from2 ), from2 ); } @Override public String toString() { return String.format( "is in the range `%s` to `%s`", min, max ); } }; } public static final Function2, Integer> port = illegalValueMessage( "must be a valid port number", range( 0, 65535 ) ); public static Function2, T> illegalValueMessage( final String message, final Function2, T> valueFunction ) { return new Function2, T>() { @Override public T apply( T from1, Function from2 ) { try { return valueFunction.apply( from1, from2 ); } catch ( IllegalArgumentException e ) { throw new IllegalArgumentException( message ); } } @Override public String toString() { String description = message; if ( valueFunction != null && !String.format( MATCHES_PATTERN_MESSAGE, ANY ).equals( valueFunction.toString() ) ) { description += " (" + valueFunction.toString() + ")"; } return description; } }; } public static Function2, String> toLowerCase = new Function2, String>() { @Override public String apply( String value, Function settings ) { return value.toLowerCase(); } }; public static Function2, URI> normalize = new Function2, URI>() { @Override public URI apply( URI value, Function settings ) { String resultStr = value.normalize().toString(); if ( resultStr.endsWith( "/" ) ) { value = java.net.URI.create( resultStr.substring( 0, resultStr.length() - 1 ) ); } return value; } }; // Setting converters and constraints public static Function2, File> basePath( final Setting baseSetting ) { return new Function2, File>() { @Override public File apply( File path, Function settings ) { File parent = baseSetting.apply( settings ); return path.isAbsolute() ? path : new File( parent, path.getPath() ); } @Override public String toString() { return "is relative to " + baseSetting.name(); } }; } public static Function2, File> isFile = new Function2, File>() { @Override public File apply( File path, Function settings ) { if ( path.exists() && !path.isFile() ) { throw new IllegalArgumentException( String.format( "%s must point to a file, not a directory", path.toString() ) ); } return path; } }; public static Function2, File> isDirectory = new Function2, File>() { @Override public File apply( File path, Function settings ) { if ( path.exists() && !path.isDirectory() ) { throw new IllegalArgumentException( String.format( "%s must point to a file, not a directory", path.toString() ) ); } return path; } }; // Setting helpers private static Function, String> named( final String name ) { return new Function, String>() { @Override public String apply( Function settings ) { return settings.apply( name ); } }; } private static Function, String> withDefault( final String defaultValue, final Function, String> lookup ) { return new Function, String>() { @Override public String apply( Function settings ) { String value = lookup.apply( settings ); if ( value == null ) { return defaultValue; } else { return value; } } }; } private static Function, String> mandatory( final Function, String> lookup ) { return new Function, String>() { @Override public String apply( Function settings ) { String value = lookup.apply( settings ); if ( value == null ) { throw new IllegalArgumentException( "mandatory setting is missing" ); } return value; } }; } private Settings() { } public static class DefaultSetting implements SettingHelper { private final String name; private final Function parser; private final Function, String> valueLookup; private final Function, String> defaultLookup; private Function2, T>[] valueConverters; public DefaultSetting( String name, Function parser, Function, String> valueLookup, Function, String> defaultLookup, Function2, T>... valueConverters ) { this.name = name; this.parser = parser; this.valueLookup = valueLookup; this.defaultLookup = defaultLookup; this.valueConverters = valueConverters; } @Override public String name() { return name; } @Override public String getDefaultValue() { return defaultLookup( Functions.nullFunction() ); } public String lookup( Function settings ) { return valueLookup.apply( settings ); } public String defaultLookup( Function settings ) { return defaultLookup.apply( settings ); } @Override public T apply( Function settings ) { // Lookup value as string String value = lookup( settings ); // Try defaults if ( value == null ) { try { value = defaultLookup( settings ); } catch ( Exception e ) { throw new IllegalArgumentException( String.format( "Missing mandatory setting '%s'", name() ) ); } } // If still null, return null if ( value == null ) { return null; } // Parse value T result; try { result = parser.apply( value ); // Apply converters and constraints for ( Function2, T> valueConverter : valueConverters ) { result = valueConverter.apply( result, settings ); } } catch ( IllegalArgumentException e ) { throw new InvalidSettingException( name(), value, e.getMessage() ); } return result; } @Override public String toString() { StringBuilder builder = new StringBuilder( ); builder.append( name ).append( " is " ).append( parser.toString() ); if (valueConverters.length > 0) { builder.append( " which " ); for ( int i = 0; i < valueConverters.length; i++ ) { Function2, T> valueConverter = valueConverters[i]; if (i > 0) builder.append( ", and " ); builder.append( valueConverter ); } } return builder.toString(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy