Skip to content

Commit

Permalink
Centralize main processing logic
Browse files Browse the repository at this point in the history
  • Loading branch information
fbiville committed Jun 23, 2017
1 parent 210485f commit 65359ef
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 156 deletions.
@@ -0,0 +1,138 @@
/*
* Copyright (c) 2002-2017 "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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.tooling.procedure;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.TypeElement;

import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.UserAggregationFunction;
import org.neo4j.procedure.UserAggregationResult;
import org.neo4j.procedure.UserAggregationUpdate;
import org.neo4j.procedure.UserFunction;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.MessagePrinter;
import org.neo4j.tooling.procedure.validators.DuplicatedExtensionValidator;

import static org.neo4j.tooling.procedure.CompilerOptions.IGNORE_CONTEXT_WARNINGS_OPTION;

/**
* Base processor that processes {@link Element} annotated with {@code T}.
* It also detects and reports duplicated elements (duplication can obviously be detected within a compilation unit and
* not globally per Neo4j instance, as explained in {@link DuplicatedExtensionValidator}.
*
* @param <T> processed annotation type
*/
public class DuplicationAwareBaseProcessor<T extends Annotation> extends AbstractProcessor
{
private final Set<Element> visitedElements = new LinkedHashSet<>();
private final Class<T> supportedAnnotationType;
private final Function<T,Optional<String>> customNameFunction;
private final Function<ProcessingEnvironment,ElementVisitor<Stream<CompilationMessage>,Void>> visitorSupplier;

private Function<Collection<Element>,Stream<CompilationMessage>> duplicationValidator;
private ElementVisitor<Stream<CompilationMessage>,Void> visitor;
private MessagePrinter messagePrinter;

/**
* Base initialization of Neo4j extension processor (where extension can be {@link Procedure}, {@link UserFunction},
* {@link UserAggregationFunction}).
*
* @param supportedAnnotationType main annotation type supported by the processor. The main annotation may depend on
* other annotations (e.g. {@link UserAggregationFunction} works with {@link UserAggregationResult} and
* {@link UserAggregationUpdate}).
* However, by design, these auxiliary annotations are processed by traversing the
* element graph, rather than by standalone annotation processors.
* @param customNameFunction function allowing to extract the custom simple name of the annotated element
* @param visitorSupplier supplies the main {@link ElementVisitor} class in charge of traversing and validating the
* annotated elements
*/
public DuplicationAwareBaseProcessor( Class<T> supportedAnnotationType, Function<T,Optional<String>> customNameFunction,
Function<ProcessingEnvironment,ElementVisitor<Stream<CompilationMessage>,Void>> visitorSupplier)
{
this.supportedAnnotationType = supportedAnnotationType;
this.customNameFunction = customNameFunction;
this.visitorSupplier = visitorSupplier;
}

@Override
public synchronized void init( ProcessingEnvironment processingEnv )
{
super.init( processingEnv );

messagePrinter = new MessagePrinter( processingEnv.getMessager() );
duplicationValidator = new DuplicatedExtensionValidator<>( processingEnv.getElementUtils(), supportedAnnotationType, customNameFunction );
visitor = visitorSupplier.apply( processingEnv );
}

@Override
public Set<String> getSupportedOptions()
{
return Collections.singleton( IGNORE_CONTEXT_WARNINGS_OPTION );
}

@Override
public Set<String> getSupportedAnnotationTypes()
{
return Collections.singleton( supportedAnnotationType.getName() );
}

@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.RELEASE_8;
}

@Override
public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv )
{
processElements( roundEnv );
if ( roundEnv.processingOver() )
{
duplicationValidator.apply( visitedElements ).forEach( messagePrinter::print );
}
return false;
}

private void processElements( RoundEnvironment roundEnv )
{
Set<? extends Element> functions = roundEnv.getElementsAnnotatedWith( supportedAnnotationType );
visitedElements.addAll( functions );
functions.stream().flatMap( this::validate ).forEachOrdered( messagePrinter::print );
}

private Stream<CompilationMessage> validate( Element element )
{
return visitor.visit( element );
}
}
Expand Up @@ -21,99 +21,30 @@

import com.google.auto.service.AutoService;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import org.neo4j.procedure.Procedure;
import org.neo4j.tooling.procedure.compilerutils.CustomNameExtractor;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.MessagePrinter;
import org.neo4j.tooling.procedure.validators.DuplicatedExtensionValidator;
import org.neo4j.tooling.procedure.visitors.ProcedureVisitor;

import static org.neo4j.tooling.procedure.CompilerOptions.IGNORE_CONTEXT_WARNINGS_OPTION;

@AutoService( Processor.class )
public class ProcedureProcessor extends AbstractProcessor
public class ProcedureProcessor extends DuplicationAwareBaseProcessor<Procedure>
{

private static final Class<Procedure> sprocType = Procedure.class;
private final Set<Element> visitedProcedures = new LinkedHashSet<>();

private Function<Collection<Element>,Stream<CompilationMessage>> duplicationValidator;
private ElementVisitor<Stream<CompilationMessage>,Void> visitor;
private MessagePrinter messagePrinter;

@Override
public Set<String> getSupportedOptions()
{
return Collections.singleton( IGNORE_CONTEXT_WARNINGS_OPTION );
}

@Override
public Set<String> getSupportedAnnotationTypes()
{
return Collections.singleton( sprocType.getName() );
}

@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.RELEASE_8;
}

@Override
public synchronized void init( ProcessingEnvironment processingEnv )
public ProcedureProcessor()
{
super.init( processingEnv );
Types typeUtils = processingEnv.getTypeUtils();
Elements elementUtils = processingEnv.getElementUtils();

visitedProcedures.clear();
messagePrinter = new MessagePrinter( processingEnv.getMessager() );
visitor = new ProcedureVisitor( typeUtils, elementUtils,
processingEnv.getOptions().containsKey( IGNORE_CONTEXT_WARNINGS_OPTION ) );
duplicationValidator =
new DuplicatedExtensionValidator<>( elementUtils, sprocType,
( proc ) -> CustomNameExtractor.getName( proc::name, proc::value ) );
super( Procedure.class, ( proc ) -> CustomNameExtractor.getName( proc::name, proc::value ),
processingEnvironment ->
{
Types typeUtils = processingEnvironment.getTypeUtils();
Elements elementUtils = processingEnvironment.getElementUtils();

return new ProcedureVisitor( typeUtils, elementUtils,
processingEnvironment.getOptions().containsKey( IGNORE_CONTEXT_WARNINGS_OPTION ) );
} );
}

@Override
public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv )
{

processElements( roundEnv );
if ( roundEnv.processingOver() )
{
duplicationValidator.apply( visitedProcedures ).forEach( messagePrinter::print );
}
return false;
}

private void processElements( RoundEnvironment roundEnv )
{
Set<? extends Element> procedures = roundEnv.getElementsAnnotatedWith( sprocType );
visitedProcedures.addAll( procedures );
procedures.stream().flatMap( this::validate ).forEachOrdered( messagePrinter::print );
}

private Stream<CompilationMessage> validate( Element element )
{
return visitor.visit( element );
}

}
Expand Up @@ -21,96 +21,31 @@

import com.google.auto.service.AutoService;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import org.neo4j.procedure.UserFunction;
import org.neo4j.tooling.procedure.compilerutils.CustomNameExtractor;
import org.neo4j.tooling.procedure.compilerutils.TypeMirrorUtils;
import org.neo4j.tooling.procedure.messages.CompilationMessage;
import org.neo4j.tooling.procedure.messages.MessagePrinter;
import org.neo4j.tooling.procedure.validators.DuplicatedExtensionValidator;
import org.neo4j.tooling.procedure.visitors.UserFunctionVisitor;

import static org.neo4j.tooling.procedure.CompilerOptions.IGNORE_CONTEXT_WARNINGS_OPTION;

@AutoService( Processor.class )
public class UserFunctionProcessor extends AbstractProcessor
public class UserFunctionProcessor extends DuplicationAwareBaseProcessor<UserFunction>
{
private static final Class<UserFunction> userFunctionType = UserFunction.class;
private final Set<Element> visitedFunctions = new LinkedHashSet<>();

private Function<Collection<Element>,Stream<CompilationMessage>> duplicationValidator;
private ElementVisitor<Stream<CompilationMessage>,Void> visitor;
private MessagePrinter messagePrinter;

@Override
public Set<String> getSupportedOptions()
{
return Collections.singleton( IGNORE_CONTEXT_WARNINGS_OPTION );
}

@Override
public Set<String> getSupportedAnnotationTypes()
{
return Collections.singleton( userFunctionType.getName() );
}

@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.RELEASE_8;
}

@Override
public synchronized void init( ProcessingEnvironment processingEnv )
{
super.init( processingEnv );
Types typeUtils = processingEnv.getTypeUtils();
Elements elementUtils = processingEnv.getElementUtils();

visitedFunctions.clear();
messagePrinter = new MessagePrinter( processingEnv.getMessager() );
visitor = new UserFunctionVisitor( typeUtils, elementUtils, new TypeMirrorUtils( typeUtils, elementUtils ),
processingEnv.getOptions().containsKey( IGNORE_CONTEXT_WARNINGS_OPTION ) );
duplicationValidator = new DuplicatedExtensionValidator<>( elementUtils, userFunctionType,
( function ) -> CustomNameExtractor.getName( function::name, function::value ) );
}

@Override
public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv )
{
processElements( roundEnv );
if ( roundEnv.processingOver() )
{
duplicationValidator.apply( visitedFunctions ).forEach( messagePrinter::print );
}
return false;
}

private void processElements( RoundEnvironment roundEnv )
{
Set<? extends Element> functions = roundEnv.getElementsAnnotatedWith( userFunctionType );
visitedFunctions.addAll( functions );
functions.stream().flatMap( this::validate ).forEachOrdered( messagePrinter::print );
}

private Stream<CompilationMessage> validate( Element element )
public UserFunctionProcessor()
{
return visitor.visit( element );
super( UserFunction.class, ( function ) -> CustomNameExtractor.getName( function::name, function::value ),
processingEnvironment ->
{
Types typeUtils = processingEnvironment.getTypeUtils();
Elements elementUtils = processingEnvironment.getElementUtils();
return new UserFunctionVisitor( typeUtils, elementUtils,
new TypeMirrorUtils( typeUtils, elementUtils ),
processingEnvironment.getOptions().containsKey( IGNORE_CONTEXT_WARNINGS_OPTION ) );
} );
}
}
Expand Up @@ -38,6 +38,17 @@

import static java.util.stream.Collectors.groupingBy;

/**
* Validates that a given extension name is not declared by multiple elements annotated with the same annotation of type
* {@code T}.
* This validation is done within an annotation processor. This means that the detection is detected only per
* compilation unit, not per Neo4j instance.
*
* Indeed, a Neo4j instance can aggregate several extension JARs and its duplication detection cannot be entirely
* replaced by this.
*
* @param <T> annotation type
*/
public class DuplicatedExtensionValidator<T extends Annotation>
implements Function<Collection<Element>,Stream<CompilationMessage>>
{
Expand Down

0 comments on commit 65359ef

Please sign in to comment.