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

react4j.processor.ViewDescriptor Maven / Gradle / Ivy

There is a newer version: 0.196
Show newest version
package react4j.processor;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import org.realityforge.proton.GeneratorUtil;
import org.realityforge.proton.MemberChecks;
import org.realityforge.proton.ProcessorException;

final class ViewDescriptor
{
  @Nonnull
  private final String _name;
  @Nonnull
  private final TypeElement _element;
  @Nonnull
  private final ViewType _type;
  private final boolean _inject;
  private final boolean _sting;
  private final boolean _hasConstructor;
  private final boolean _hasPostConstruct;
  private final boolean _shouldSetDefaultPriority;
  private final boolean _requireRender;
  @Nonnull
  private final ExecutableElement _constructor;
  @Nullable
  private ExecutableElement _render;
  @Nullable
  private ExecutableElement _preUpdate;
  @Nullable
  private ExecutableElement _postRender;
  @Nullable
  private ExecutableElement _postUpdate;
  @Nullable
  private ExecutableElement _postMount;
  @Nullable
  private ExecutableElement _onError;
  /**
   * Methods that are inputs accessors.
   * These should be implemented as accesses to the underlying inputs value.
   */
  @Nullable
  private List _inputs;
  /**
   * Methods that are inputs accessors.
   * These should be implemented as accesses to the underlying inputs value.
   */
  @Nullable
  private List _onInputChangeDescriptors;
  /**
   * Descriptors for methods annotated by @ScheduleRender.
   */
  @Nullable
  private List _scheduleRenderDescriptors;
  /**
   * Descriptors for methods annotated by @Publish.
   */
  @Nullable
  private List _publishDescriptors;
  private Boolean _validateInputs;
  private Boolean _viewAccessesDeprecatedElements;
  private Boolean _builderAccessesDeprecatedElements;

  ViewDescriptor( @Nonnull final String name,
                  @Nonnull final TypeElement element,
                  @Nonnull final ExecutableElement constructor,
                  @Nonnull final ViewType type,
                  final boolean inject,
                  final boolean sting,
                  final boolean hasConstructor,
                  final boolean hasPostConstruct,
                  final boolean shouldSetDefaultPriority,
                  final boolean requireRender )
  {
    _name = Objects.requireNonNull( name );
    _element = Objects.requireNonNull( element );
    _constructor = Objects.requireNonNull( constructor );
    _type = Objects.requireNonNull( type );
    _inject = inject;
    _sting = sting;
    _hasConstructor = hasConstructor;
    _hasPostConstruct = hasPostConstruct;
    _shouldSetDefaultPriority = shouldSetDefaultPriority;
    _requireRender = requireRender;
  }

  @Nonnull
  String keySuffix()
  {
    return "_" + _element.getSimpleName() + "_" + shortSha( _element.getQualifiedName().toString() );
  }

  @Nonnull
  ExecutableElement getConstructor()
  {
    return _constructor;
  }

  private boolean hasConstructorParams()
  {
    return !_constructor.getParameters().isEmpty();
  }

  boolean enableInject()
  {
    return _inject;
  }

  boolean enableSting()
  {
    return _sting;
  }

  boolean hasConstructor()
  {
    return _hasConstructor;
  }

  boolean hasPostConstruct()
  {
    return _hasPostConstruct;
  }

  boolean shouldSetDefaultPriority()
  {
    return _shouldSetDefaultPriority;
  }

  boolean requireRender()
  {
    return _requireRender;
  }

  @Nonnull
  String getPackageName()
  {
    return GeneratorUtil.getQualifiedPackageName( _element );
  }

  @Nonnull
  String getName()
  {
    return _name;
  }

  @Nonnull
  ClassName getClassName()
  {
    return ClassName.get( getElement() );
  }

  @Nonnull
  TypeElement getElement()
  {
    return _element;
  }

  @Nonnull
  DeclaredType getDeclaredType()
  {
    return (DeclaredType) _element.asType();
  }

  @Nonnull
  ClassName getEnhancedClassName()
  {
    return GeneratorUtil.getGeneratedClassName( _element, "React4j_", "" );
  }

  @Nonnull
  ClassName getBuilderClassName()
  {
    return GeneratorUtil.getGeneratedClassName( _element, "", "Builder" );
  }

  @Nonnull
  ClassName getFactoryClassName()
  {
    return GeneratorUtil.getGeneratedClassName( _element, "", "Factory" );
  }

  @Nonnull
  ClassName getArezClassName()
  {
    final String simpleName = "Arez_" + GeneratorUtil.getGeneratedSimpleClassName( _element, "React4j_", "" );
    return ClassName.get( getPackageName(), simpleName );
  }

  @Nonnull
  TypeName getViewType()
  {
    final List typeNames =
      getDeclaredType().getTypeArguments().stream().map( TypeName::get ).collect( Collectors.toList() );
    if ( !typeNames.isEmpty() )
    {
      return ParameterizedTypeName.get( ClassName.get( getElement() ), typeNames.toArray( new TypeName[ 0 ] ) );
    }
    else
    {
      return ClassName.get( getElement() );
    }
  }

  boolean needsInjection()
  {
    return hasConstructorParams();
  }

  boolean trackRender()
  {
    return ( ViewType.MAYBE_TRACKING == _type || ViewType.TRACKING == _type ) && hasRender();
  }

  @Nonnull
  ViewType getType()
  {
    return _type;
  }

  int syntheticKeyParts()
  {
    return (int) getInputs().stream().filter( InputDescriptor::isImmutable ).count();
  }

  @Nonnull
  List getInputs()
  {
    assert null != _inputs;
    return _inputs;
  }

  void setInputs( @Nonnull final List events )
  {
    _inputs = Objects.requireNonNull( events );
  }

  @Nullable
  InputDescriptor findInputNamed( @Nonnull final String name )
  {
    return getInputs().stream().filter( p -> p.getName().equals( name ) ).findAny().orElse( null );
  }

  /**
   * Needs to be invoked after all the inputs have been completely constructed.
   */
  void sortInputs()
  {
    assert null != _inputs;
    _inputs.sort( InputComparator.COMPARATOR );
  }

  @Nonnull
  List getPreUpdateOnInputChangeDescriptors()
  {
    return getOnInputChangeDescriptors()
      .stream()
      .filter( OnInputChangeDescriptor::isPreUpdate )
      .collect( Collectors.toList() );
  }

  @Nonnull
  List getPostUpdateOnInputChangeDescriptors()
  {
    return getOnInputChangeDescriptors().stream().filter( o -> !o.isPreUpdate() ).collect( Collectors.toList() );
  }

  @Nonnull
  private List getOnInputChangeDescriptors()
  {
    assert null != _onInputChangeDescriptors;
    return _onInputChangeDescriptors;
  }

  void setOnInputChangeDescriptors( @Nonnull List onInputChangeDescriptors )
  {
    _onInputChangeDescriptors = Objects.requireNonNull( onInputChangeDescriptors );
  }

  @Nonnull
  List getScheduleRenderDescriptors()
  {
    assert null != _scheduleRenderDescriptors;
    return _scheduleRenderDescriptors;
  }

  void setScheduleRenderDescriptors( @Nonnull final List scheduleRenderDescriptors )
  {
    _scheduleRenderDescriptors = Objects.requireNonNull( scheduleRenderDescriptors );
  }

  @Nonnull
  List getPublishDescriptors()
  {
    assert null != _publishDescriptors;
    return _publishDescriptors;
  }

  void setPublishDescriptors( @Nonnull final List publishDescriptors )
  {
    _publishDescriptors = Objects.requireNonNull( publishDescriptors );
  }

  boolean hasObservableInputs()
  {
    return getInputs().stream().anyMatch( InputDescriptor::isObservable );
  }

  @Nullable
  ExecutableElement getPreUpdate()
  {
    return _preUpdate;
  }

  void setPreUpdate( @Nonnull final ExecutableElement preUpdate )
    throws ProcessorException
  {
    if ( null != _preUpdate )
    {
      throw new ProcessorException( "@PreUpdate target duplicates existing method named " + _preUpdate.getSimpleName(),
                                    preUpdate );
    }
    else
    {
      _preUpdate = preUpdate;
    }
  }

  boolean hasRender()
  {
    return null != _render;
  }

  @Nonnull
  ExecutableElement getRender()
  {
    assert null != _render;
    return _render;
  }

  void setRender( @Nonnull final ExecutableElement render )
  {
    if ( null != _render )
    {
      throw new ProcessorException( MemberChecks.mustNot( Constants.RENDER_CLASSNAME,
                                                          "be present when another method named " +
                                                          _render.getSimpleName() +
                                                          " exists with the same annotation" ), render );
    }
    _render = Objects.requireNonNull( render );
  }

  @Nullable
  ExecutableElement getPostRender()
  {
    return _postRender;
  }

  void setPostRender( @Nonnull final ExecutableElement postRender )
    throws ProcessorException
  {
    if ( null != _postRender )
    {
      throw new ProcessorException( "@PostMountOrUpdate target duplicates existing method named " +
                                    _postRender.getSimpleName(), postRender );
    }
    else
    {
      _postRender = postRender;
    }
  }

  @Nullable
  ExecutableElement getPostUpdate()
  {
    return _postUpdate;
  }

  void setPostUpdate( @Nonnull final ExecutableElement postUpdate )
    throws ProcessorException
  {
    if ( null != _postUpdate )
    {
      throw new ProcessorException( "@PostUpdate target duplicates existing method named " +
                                    _postUpdate.getSimpleName(), postUpdate );
    }
    else
    {
      _postUpdate = postUpdate;
    }
  }

  @Nullable
  ExecutableElement getPostMount()
  {
    return _postMount;
  }

  void setPostMount( @Nonnull final ExecutableElement postMount )
    throws ProcessorException
  {
    if ( null != _postMount )
    {
      throw new ProcessorException( "@PostMount target duplicates existing method named " + _postMount.getSimpleName(),
                                    postMount );
    }
    else
    {
      _postMount = postMount;
    }
  }

  @Nullable
  ExecutableElement getOnError()
  {
    return _onError;
  }

  void setOnError( @Nonnull final ExecutableElement onError )
    throws ProcessorException
  {

    if ( null != _onError )
    {
      throw new ProcessorException( "@OnError target duplicates existing method named " + _onError.getSimpleName(),
                                    onError );
    }
    else
    {
      _onError = onError;
    }
  }

  private boolean shouldGenerateLifecycle()
  {
    return generateComponentDidMount() ||
           generateShouldComponentUpdate() ||
           generateComponentPreUpdate() ||
           generateComponentDidUpdate() ||
           generateComponentWillUnmount() ||
           generateComponentDidCatch();
  }

  boolean shouldGenerateLiteLifecycle()
  {
    return ( generateComponentDidUpdateInLiteLifecycle() != generateComponentDidUpdate() ||
             generateComponentWillUnmountInLiteLifecycle() != generateComponentWillUnmount() ||
             generateShouldComponentUpdateInLiteLifecycle() != generateShouldComponentUpdate() ||
             generateComponentDidMountInLiteLifecycle() != generateComponentDidMount() ) &&
           shouldGenerateLifecycle();
  }

  boolean generateShouldComponentUpdate()
  {
    return generateShouldComponentUpdateInLiteLifecycle() || shouldValidateInputs();
  }

  boolean generateShouldComponentUpdateInLiteLifecycle()
  {
    return true; // type != STATELESS || hasObservableInputs() || hasUpdateOnChangeInputs();
  }

  boolean generateComponentDidCatch()
  {
    return null != _onError;
  }

  boolean generateComponentWillUnmountInLiteLifecycle()
  {
    return true; // type == STATELESS || type == TRACKING || MAYBE_TRACKING
  }

  boolean generateComponentWillUnmount()
  {
    return true; // type == STATELESS || type == TRACKING || MAYBE_TRACKING
  }

  boolean generateComponentPreUpdate()
  {
    return hasPreUpdateOnInputChange() || null != _preUpdate;
  }

  boolean generateComponentDidMount()
  {
    return generateComponentDidMountInLiteLifecycle() ||
           // We do it when tracking render so we can store debug information in state
           trackRender();
  }

  boolean generateComponentDidMountInLiteLifecycle()
  {
    return null != _postMount || null != _postRender;
  }

  boolean hasPreUpdateOnInputChange()
  {
    return !getPreUpdateOnInputChangeDescriptors().isEmpty();
  }

  boolean hasPostUpdateOnInputChange()
  {
    return !getPostUpdateOnInputChangeDescriptors().isEmpty();
  }

  boolean generateComponentDidUpdate()
  {
    return generateComponentDidUpdateInLiteLifecycle() ||
           // We do it when tracking render so we can store debug information in state
           trackRender();
  }

  boolean generateComponentDidUpdateInLiteLifecycle()
  {
    return hasPostUpdateOnInputChange() || null != _postUpdate || null != _postRender;
  }

  boolean shouldValidateInputs()
  {
    if ( null == _validateInputs )
    {
      _validateInputs = getInputs()
        .stream()
        .anyMatch( input -> input.hasValidateMethod() ||
                            ( input.isNonNull() && ( input.isRequired() || input.isContextSource() ) ) );
    }
    return _validateInputs;
  }

  boolean viewAccessesDeprecatedElements()
  {
    if ( null == _viewAccessesDeprecatedElements )
    {
      _viewAccessesDeprecatedElements =
        isDeprecated( _element ) ||
        isDeprecated( _constructor ) ||
        isDeprecated( _preUpdate ) ||
        isDeprecated( _postRender ) ||
        isDeprecated( _postMount ) ||
        isDeprecated( _postUpdate ) ||
        isDeprecated( _onError ) ||
        getInputs().stream()
          .anyMatch( p -> isDeprecated( p.getMethod() ) ||
                          p.hasValidateMethod() && isDeprecated( p.getValidateMethod() ) ) ||
        getPostUpdateOnInputChangeDescriptors().stream().anyMatch( d -> isDeprecated( d.getMethod() ) );
    }
    return _viewAccessesDeprecatedElements;
  }

  boolean builderAccessesDeprecatedElements()
  {
    if ( null == _builderAccessesDeprecatedElements )
    {
      _builderAccessesDeprecatedElements =
        isDeprecated( _element ) ||
        getInputs().stream().anyMatch( p -> p.hasDefaultMethod() && isDeprecated( p.getDefaultMethod() ) ||
                                            p.hasDefaultField() && isDeprecated( p.getDefaultField() ) );
    }
    return _builderAccessesDeprecatedElements;
  }

  private boolean isDeprecated( @Nullable final Element element )
  {
    return null != element && null != element.getAnnotation( Deprecated.class );
  }

  @Nonnull
  private String shortSha( @Nonnull final String text )
  {
    try
    {
      final MessageDigest algo = MessageDigest.getInstance( "SHA-1" );
      final byte[] digest = algo.digest( text.getBytes() );
      final StringBuilder sb = new StringBuilder();
      for ( int i = 0; i < 4; i++ )
      {
        final byte value = digest[ i ];
        sb.append( Integer.toString( ( value & 0xff ) + 0x100, 16 ).substring( 1 ) );
      }
      return sb.toString();
    }
    catch ( final NoSuchAlgorithmException e )
    {
      throw new IllegalStateException( e );
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy