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

com.github.nalukit.nalu.processor.NaluPluginGwtProcessor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018 - Frank Hossfeld
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not
 *  use this file except in compliance with the License. You may obtain a copy of
 *  the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 *  License for the specific language governing permissions and limitations under
 *  the License.
 */

package com.github.nalukit.nalu.processor;

import com.github.nalukit.nalu.client.Nalu;
import com.github.nalukit.nalu.plugin.gwt.client.annotation.Selector;
import com.github.nalukit.nalu.plugin.gwt.client.selector.AbstractSelectorProvider;
import com.github.nalukit.nalu.plugin.gwt.client.selector.IsSelectorProvider;
import com.github.nalukit.nalu.plugin.gwt.client.selector.SelectorCommand;
import com.github.nalukit.nalu.plugin.gwt.client.selector.SelectorProvider;
import com.github.nalukit.nalu.processor.model.intern.ClassNameModel;
import com.google.auto.service.AutoService;
import com.google.common.base.Stopwatch;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;

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.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

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

@AutoService(Processor.class)
public class NaluPluginGwtProcessor
    extends AbstractProcessor {

  private static final String IMPL_NAME = "SelectorProviderImpl";

  private ProcessorUtils                        processorUtils;
  private Stopwatch                             stopwatch;
  private Map> models;

  public NaluPluginGwtProcessor() {
    super();
  }

  @Override
  public Set getSupportedAnnotationTypes() {
    return Stream.of(Selector.class.getCanonicalName())
                 .collect(toSet());
  }

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

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    this.stopwatch = Stopwatch.createStarted();
    setUp();
    this.processorUtils.createNoteMessage("Nalu-Plugin-GWT-Processor started ...");
    String implementationVersion = Nalu.getVersion();
    this.processorUtils.createNoteMessage("Nalu-Plugin-GWT-Processor version >>" + implementationVersion + "<<");
  }

  @SuppressWarnings("unused")
  @Override
  public boolean process(Set annotations,
                         RoundEnvironment roundEnv) {
    try {
      if (roundEnv.processingOver()) {
        this.processorUtils.createNoteMessage("Nalu-Plugin-GWT-Processor finished ... processing takes: " +
                                              this.stopwatch.stop()
                                                            .toString());
      } else {
        if (!annotations.isEmpty()) {
          for (TypeElement annotation : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(Selector.class)) {
              validate(element);
              // get enclosing element
              Element enclosingElement = element.getEnclosingElement();
              // get Annotation
              Selector selectorAnnotation = element.getAnnotation(Selector.class);
              // add data
              this.models.computeIfAbsent(enclosingElement,
                                          s -> new ArrayList<>())
                         .add(new SelectorMetaModel(selectorAnnotation.value(),
                                                    enclosingElement.toString(),
                                                    element));
            }
          }
          // generate providers
          for (Element k : this.models.keySet()) {
            this.generate(k,
                          this.models.get(k));
          }
        }
      }
    } catch (ProcessorException e) {
      this.processorUtils.createErrorMessage(e.getMessage());
      return true;
    }
    return true;
  }

  private void validate(Element element)
      throws ProcessorException {
    // @AcceptParameter can only be used on a method
    if (!ElementKind.METHOD.equals(element.getKind())) {
      throw new ProcessorException("Nalu-Processor: @Selector can only be used with a method");
    }
    ExecutableElement executableElement = (ExecutableElement) element;
    if (executableElement.getParameters()
                         .size() != 1) {
      throw new ProcessorException("Nalu-Processor: @Selector can only be used with a method that has one parameter");
    }
    String parameterClass = executableElement.getParameters()
                                             .get(0)
                                             .asType()
                                             .toString();
    if (!(IsWidget.class.getCanonicalName()
                        .equals(parameterClass) ||
          Widget.class.getCanonicalName()
                      .equals(parameterClass))) {
      throw new ProcessorException("Nalu-Processor: @Selector can only be used with a method that has one parameter and the parameter type is com.google.gwt.user.client.ui.IsWidget or com.google.gwt.user.client.ui.Widget");
    }
  }

  private void generate(Element enclosingElement,
                        List models)
      throws ProcessorException {
    ClassNameModel enclosingClassNameModel = new ClassNameModel(enclosingElement.toString());
    TypeSpec.Builder typeSpec = TypeSpec.classBuilder(enclosingElement.getSimpleName() + NaluPluginGwtProcessor.IMPL_NAME)
                                        .superclass(ClassName.get(AbstractSelectorProvider.class))
                                        .addModifiers(Modifier.PUBLIC,
                                                      Modifier.FINAL)
                                        .addSuperinterface(ParameterizedTypeName.get(ClassName.get(IsSelectorProvider.class),
                                                                                     enclosingClassNameModel.getTypeName()));

    // constructor ...
    MethodSpec constructor = MethodSpec.constructorBuilder()
                                       .addModifiers(Modifier.PUBLIC)
                                       .addStatement("super()")
                                       .build();
    typeSpec.addMethod(constructor);

    // method "initialize"
    MethodSpec.Builder initializeMethod = MethodSpec.methodBuilder("initialize")
                                                    .addModifiers(Modifier.PUBLIC,
                                                                  Modifier.FINAL)
                                                    .addParameter(ParameterSpec.builder(ClassName.get(enclosingClassNameModel.getPackage(),
                                                                                                      enclosingClassNameModel.getSimpleName()),
                                                                                        "component")
                                                                               .build())
                                                    .addAnnotation(Override.class);
    models.forEach(model -> {
      initializeMethod.addStatement("$T.get().getSelectorCommands().put($S, $L)",
                                    ClassName.get(SelectorProvider.class),
                                    model.selector,
                                    TypeSpec.anonymousClassBuilder("")
                                            .addSuperinterface(SelectorCommand.class)
                                            .addMethod(MethodSpec.methodBuilder("append")
                                                                 .addAnnotation(Override.class)
                                                                 .addModifiers(Modifier.PUBLIC)
                                                                 .addParameter(ParameterSpec.builder(ClassName.get(IsWidget.class),
                                                                                                     "widget")
                                                                                            .build())
                                                                 .addStatement("component.$L(widget.asWidget())",
                                                                               model.getSelectorElement()
                                                                                    .getSimpleName()
                                                                                    .toString())
                                                                 .build())
                                            .build())
                      .build();
    });
    typeSpec.addMethod(initializeMethod.build());

    // method "remove"
    MethodSpec.Builder removeMethod = MethodSpec.methodBuilder("removeSelectors")
                                                .addModifiers(Modifier.PUBLIC,
                                                              Modifier.FINAL)
                                                .addAnnotation(Override.class);
    models.forEach(model -> {
      removeMethod.addStatement("$T.get().getSelectorCommands().remove($S)",
                                ClassName.get(SelectorProvider.class),
                                model.getSelector())
                  .build();
    });
    typeSpec.addMethod(removeMethod.build());

    JavaFile javaFile = JavaFile.builder(enclosingClassNameModel.getPackage(),
                                         typeSpec.build())
                                .build();
    try {
      //      System.out.println(javaFile.toString());
      javaFile.writeTo(this.processingEnv.getFiler());
    } catch (IOException e) {
      throw new ProcessorException("Unable to write generated file: >>" +
                                   enclosingElement.getSimpleName() +
                                   NaluPluginGwtProcessor.IMPL_NAME +
                                   "<< -> exception: " +
                                   e.getMessage());
    }
  }

  private void setUp() {
    this.models = new HashMap<>();

    this.processorUtils = ProcessorUtils.builder()
                                        .processingEnvironment(processingEnv)
                                        .build();
  }

  static class SelectorMetaModel {

    private final String selector;

    private final String enclosingElement;

    private final Element selectorElement;

    public SelectorMetaModel(String selector,
                             String enclosingElement,
                             Element selectorElement) {
      this.selector         = selector;
      this.enclosingElement = enclosingElement;
      this.selectorElement  = selectorElement;
    }

    public String getSelector() {
      return selector;
    }

    public String getEnclosingElement() {
      return enclosingElement;
    }

    public Element getSelectorElement() {
      return selectorElement;
    }

    public ClassNameModel getEnclosingElementModel() {
      return new ClassNameModel(this.enclosingElement);
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy