Package org.springframework.boot.config.processor

Source Code of org.springframework.boot.config.processor.Processor

/*
* Copyright 2012-2014 the original author or authors.
*
* 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 org.springframework.boot.config.processor;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
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.TypeKind;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.springframework.boot.config.ConfigMetadataGroup;
import org.springframework.boot.config.ConfigMetadataItem;
import org.springframework.boot.config.ConfigMetadataRepository;
import org.springframework.boot.config.SimpleConfigMetadataRepository;
import org.springframework.boot.config.processor.mapper.ConfigMetadataRepositoryJsonMapper;
import org.springframework.boot.config.processor.mapper.ConfigMetadataRepositoryMapper;
import org.springframework.boot.config.processor.model.EntityModel;
import org.springframework.boot.config.processor.model.EntityModelFactory;
import org.springframework.boot.config.processor.model.PropertyModel;
import org.springframework.boot.config.processor.util.ModelHelper;
import org.springframework.boot.config.processor.util.ModelUtils;

/**
*
* @author Stephane Nicoll
*/
class Processor {

  static final String JSON_METADATA_LOCATION = "META-INF/boot/config-metadata.json";

  private final ProcessingEnvironment env;

  private final ModelHelper modelHelper;

  private final Messager msg;

  private final Set<? extends Element> allConfigurationPropertiesElements;

  private final Set<? extends Element> allConditionElements;

  Processor(ProcessingEnvironment env, RoundEnvironment roundEnv) {
    this.env = env;
    this.modelHelper = new ModelHelper(env);
    this.msg = env.getMessager();
    this.allConfigurationPropertiesElements =
        roundEnv.getElementsAnnotatedWith(getConfigurationPropertiesType());
    this.allConditionElements =
        roundEnv.getElementsAnnotatedWith(getConditionalOnPropertyType());
  }

  /**
   * Detect and parse the configuration entities defined in the current
   * source code. Write the corresponding metadata to disk
   */
  public void process() {
    if (!shouldRun()) { // Invalidates compilation round when we have nothing to do
      return;
    }

    Collection<EntityModel> model = parseEntityModel();
    msg.printMessage(Diagnostic.Kind.NOTE, "Processing '" + model.size() + "' entities.");

    // Build the initial repository
    SimpleConfigMetadataRepository repository = buildConfigMetadataRepository(model);

    // Post process the detect the conditions
    harvestConditions(repository);

    // Write the result
    try {
      writeConfigMetadataRepository(repository);
    }
    catch (IOException e) {
      msg.printMessage(Diagnostic.Kind.ERROR, "Failed to write config metadata file (" + e.getMessage() + ")");
    }
  }

  private boolean shouldRun() {
    return !this.allConfigurationPropertiesElements.isEmpty() || !this.allConditionElements.isEmpty();
  }

  private Collection<EntityModel> parseEntityModel() {
    EntityModelFactory factory = new EntityModelFactory(env);
    Collection<EntityModel> result = new ArrayList<EntityModel>();
    for (TypeElement element : getTypeElementCandidates()) {
      EntityModel entityModel = factory.create(element);
      result.add(entityModel);
    }
    for (ExecutableElement method : getExecutableElementCandidates()) {
      if (isMethodCandidate(method)) {
        TypeElement element = modelHelper.safeToTypeElement(method.getReturnType());
        if (element != null) {
          EntityModel entityModel = factory.create(element, method);
          result.add(entityModel);
        }
      }
    }
    return result;
  }

  private SimpleConfigMetadataRepository buildConfigMetadataRepository(Collection<EntityModel> content) {
    SimpleConfigMetadataRepository repository = new SimpleConfigMetadataRepository();
    for (EntityModel entityModel : content) {
      if (!entityModel.isEmpty()) { // Register group only if the model is not empty
        ConfigMetadataGroup rootGroup = ConfigMetadataGroup.root(entityModel.getPrefix());
        fillConfigMetadataGroup(rootGroup, entityModel);
        repository.registerRootGroup(rootGroup);
      }
    }
    return repository;
  }

  private void harvestConditions(SimpleConfigMetadataRepository repository) {
    ConditionHarvester harvester = new ConditionHarvester(repository);
    for (Element conditionElement : allConditionElements) {
      harvester.harvest(conditionElement);
    }
  }

  private void writeConfigMetadataRepository(ConfigMetadataRepository repository) throws IOException {
    ConfigMetadataRepositoryMapper mapper = new ConfigMetadataRepositoryJsonMapper();
    FileObject fileObject = env.getFiler().createResource(
        StandardLocation.CLASS_OUTPUT, "", JSON_METADATA_LOCATION);
    OutputStream out = fileObject.openOutputStream();
    try {
      mapper.writeRepository(repository, out);
    }
    finally {
      out.close();
    }
  }

  private Collection<TypeElement> getTypeElementCandidates() {
    Collection<TypeElement> candidates = new ArrayList<TypeElement>();
    for (Element element : allConfigurationPropertiesElements) {
      if (element instanceof TypeElement) {
        candidates.add((TypeElement) element);
      }
    }
    return candidates;
  }

  private Collection<ExecutableElement> getExecutableElementCandidates() {
    Collection<ExecutableElement> candidates = new ArrayList<ExecutableElement>();
    for (Element element : allConfigurationPropertiesElements) {
      if (element instanceof ExecutableElement) {
        candidates.add((ExecutableElement) element);
      }
    }
    return candidates;
  }

  private boolean isMethodCandidate(ExecutableElement method) {
    return method.getModifiers().contains(Modifier.PUBLIC)
        && method.getParameters().isEmpty()
        && (TypeKind.VOID != method.getReturnType().getKind());
  }

  private void fillConfigMetadataGroup(ConfigMetadataGroup group, EntityModel model) {
    StringBuilder sb = new StringBuilder();
    generateType(sb, model.getTypeElement());
    String groupType = sb.toString();
    group.addType(groupType);
    for (PropertyModel property : model.getProperties().values()) {
      group.registerItem(toConfigMetadataItem(property, groupType));
    }
    for (EntityModel nestedEntity : model.getNestedEntities().values()) {
      ConfigMetadataGroup nestedGroup = group.registerGroup(nestedEntity.getPrefix());
      fillConfigMetadataGroup(nestedGroup, nestedEntity);
    }
  }

  private ConfigMetadataItem toConfigMetadataItem(PropertyModel propertyModel, String groupType) {
    ConfigMetadataItem item = new ConfigMetadataItem(propertyModel.getName());
    item.setValueType(propertyModel.getType());
    item.setDescription(propertyModel.getDescription());
    item.addGroupType(groupType);
    return item;
  }

  /**
   * Generate a type that is similar to what {@code java.lang.Class#getName()}
   * would return. Allows to identify inner classes with the '$' symbol.
   */
  private String generateType(StringBuilder sb, Element typeElement) {
    Element enclosingElement = typeElement.getEnclosingElement();
    if (enclosingElement.getKind() == ElementKind.PACKAGE) {
      sb.append(typeElement.toString());
    }
    if (enclosingElement.getKind() == ElementKind.CLASS) {
      generateType(sb, enclosingElement);
      sb.append("$").append(typeElement.getSimpleName());
    }
    return sb.toString();
  }

  private TypeElement getConfigurationPropertiesType() {
    return env.getElementUtils().getTypeElement(ModelUtils.CONFIGURATION_PROPERTIES_FQN);
  }

  private TypeElement getConditionalOnPropertyType() {
    return env.getElementUtils().getTypeElement(ModelUtils.CONDITIONAL_ON_PROPERTY_FQN);
  }
}
TOP

Related Classes of org.springframework.boot.config.processor.Processor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.