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

org.neo4j.kernel.impl.proc.MethodSignatureCompiler Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB 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 .
 */
/*
 * Copyright (c) 2002-2020 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.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.kernel.impl.proc;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.procs.DefaultParameterValue;
import org.neo4j.internal.kernel.api.procs.FieldSignature;
import org.neo4j.internal.kernel.api.procs.Neo4jTypes;
import org.neo4j.internal.kernel.api.procs.ProcedureSignature;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.impl.proc.TypeMappers.DefaultValueConverter;
import org.neo4j.procedure.Name;

import static org.neo4j.internal.kernel.api.procs.FieldSignature.inputField;

/**
 * Given a java method, figures out a valid {@link ProcedureSignature} field signature.
 * Basically, it takes the java signature and spits out the same signature described as ONgDB types.
 */
public class MethodSignatureCompiler
{
    private final TypeMappers typeMappers;

    public MethodSignatureCompiler( TypeMappers typeMappers )
    {
        this.typeMappers = typeMappers;
    }

    public List inputTypesFor( Method method ) throws ProcedureException
    {
        Type[] types = method.getGenericParameterTypes();
        List neoTypes = new ArrayList<>( types.length );
        for ( Type type : types )
        {
            neoTypes.add( typeMappers.toNeo4jType( type ) );
        }

        return neoTypes;
    }

    public List signatureFor( Method method ) throws ProcedureException
    {
        Parameter[] params = method.getParameters();
        Type[] types = method.getGenericParameterTypes();
        List 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 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 ONgDB type: %s",
                        name, i, method.getName(), param.getType().getSimpleName(),
                        e.getMessage() );
            }

        }

        return signature;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy