package org.realityforge.arez.processor;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.lang.annotation.Documented;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;

@SuppressWarnings( "Duplicates" )
final class ProcessorUtil
{
  private ProcessorUtil()
  {
  }

  @Nonnull
  static List<TypeVariableName> getTypeArgumentsAsNames( @Nonnull final DeclaredType declaredType )
  {
    final List<TypeVariableName> variables = new ArrayList<>();
    for ( final TypeMirror argument : declaredType.getTypeArguments() )
    {
      variables.add( TypeVariableName.get( (TypeVariable) argument ) );
    }
    return variables;
  }

  @Nonnull
  static TypeName[] getTypeArgumentsAsTypeNames( @Nonnull final DeclaredType declaredType )
  {
    final List<TypeVariableName> variables = getTypeArgumentsAsNames( declaredType );
    return variables.toArray( new TypeName[ variables.size() ] );
  }

  @Nonnull
  static List<ExecutableElement> getMethods( @Nonnull final TypeElement element )
  {
    final Map<String, ExecutableElement> methodMap = new LinkedHashMap<>();
    enumerateMethods( element, methodMap );
    return new ArrayList<>( methodMap.values() );
  }

  private static void enumerateMethods( @Nonnull final TypeElement element,
                                        @Nonnull final Map<String, ExecutableElement> methods )
  {
    for ( final TypeMirror interfaceType : element.getInterfaces() )
    {
      final TypeElement interfaceElement = (TypeElement) ( (DeclaredType) interfaceType ).asElement();
      enumerateMethods( interfaceElement, methods );
    }
    for ( final Element member : element.getEnclosedElements() )
    {
      if ( member.getKind() == ElementKind.METHOD )
      {
        methods.put( member.getSimpleName().toString(), (ExecutableElement) member );
      }
    }
  }

  @Nonnull
  static List<ExecutableElement> getConstructors( @Nonnull final TypeElement element )
  {
    return element.getEnclosedElements().stream().
      filter( m -> m.getKind() == ElementKind.CONSTRUCTOR ).
      map( m -> (ExecutableElement) m ).
      collect( Collectors.toList() );
  }

  static void copyAccessModifiers( @Nonnull final TypeElement element, @Nonnull final TypeSpec.Builder builder )
  {
    if ( element.getModifiers().contains( Modifier.PUBLIC ) )
    {
      builder.addModifiers( Modifier.PUBLIC );
    }
    else if ( element.getModifiers().contains( Modifier.PROTECTED ) )
    {
      builder.addModifiers( Modifier.PROTECTED );
    }
    else if ( element.getModifiers().contains( Modifier.PRIVATE ) )
    {
      builder.addModifiers( Modifier.PRIVATE );
    }
  }
}

@@ -3393,11 +3393,522 @@
    %7D%0A  %7D
+%0A%0A  static void copyAccessModifiers( @Nonnull final ExecutableElement element, @Nonnull final MethodSpec.Builder builder )%0A  %7B%0A    if ( element.getModifiers().contains( Modifier.PUBLIC ) )%0A    %7B%0A      builder.addModifiers( Modifier.PUBLIC );%0A    %7D%0A    else if ( element.getModifiers().contains( Modifier.PROTECTED ) )%0A    %7B%0A      builder.addModifiers( Modifier.PROTECTED );%0A    %7D%0A    else if ( element.getModifiers().contains( Modifier.PRIVATE ) )%0A    %7B%0A      builder.addModifiers( Modifier.PRIVATE );%0A    %7D%0A  %7D
 %0A%7D%0A

Add mechanism for copying access modifiers for methods