class MethodSignatureCompiler {

    public List<FieldSignature> signatureFor( Method method ) throws ProcedureException
    {
        Parameter[] params = method.getParameters();
        Type[] types = method.getGenericParameterTypes();
        List<FieldSignature> signature = new ArrayList<>( params.length );
        boolean seenDefault = false;
        for ( int i = 0; i < params.length; i++ )
        {
            Parameter param = params[i];
            Type type = types[i];

            if ( !param.isAnnotationPresent( Name.class ) )
            {
                throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed,
                        "Argument at position %d in method `%s` is missing an `@%s` annotation.%n" +
                        "Please add the annotation, recompile the class and try again.",
                        i, method.getName(), Name.class.getSimpleName() );
            }
            Name parameter = param.getAnnotation( Name.class );
            String name = parameter.value();

            if ( name.trim().length() == 0 )
            {
                throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed,
                        "Argument at position %d in method `%s` is annotated with a name,%n" +
                        "but the name is empty, please provide a non-empty name for the argument.",
                        i, method.getName() );
            }

            try
            {
                DefaultValueConverter valueConverter = typeMappers.converterFor( type );
                Optional<DefaultParameterValue> defaultValue = valueConverter.defaultValue( parameter );
                //it is not allowed to have holes in default values
                if ( seenDefault && !defaultValue.isPresent() )
                {
                    throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed,
                            "Non-default argument at position %d with name %s in method %s follows default argument. " +
                            "Add a default value or rearrange arguments so that the non-default values comes first.",
                            i, parameter.value(), method.getName() );
                }

                seenDefault = defaultValue.isPresent();

                // Currently only byte[] is not supported as a Cypher type, so we have specific conversion here.
                // Should we add more unsupported types we should generalize this.
                if ( type == byte[].class )
                {
                    FieldSignature.InputMapper mapper = new ByteArrayConverter();
                    signature.add( defaultValue.map( neo4jValue -> inputField( name, valueConverter.type(), neo4jValue, mapper ) ).orElseGet(
                            () -> inputField( name, valueConverter.type(), mapper ) ) );
                }
                else
                {
                    signature.add( defaultValue.map( neo4jValue -> inputField( name, valueConverter.type(), neo4jValue ) ).orElseGet(
                            () -> inputField( name, valueConverter.type() ) ) );
                }
            }
            catch ( ProcedureException e )
            {
                throw new ProcedureException( e.status(),
                        "Argument `%s` at position %d in `%s` with%n" +
                        "type `%s` cannot be converted to a Neo4j type: %s",
                        name, i, method.getName(), param.getType().getSimpleName(),
                        e.getMessage() );
            }

        }

        return signature;
    }

    public  MethodSignatureCompiler( TypeMappers typeMappers );

}