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

com.devonfw.cobigen.impl.extension.PluginRegistry Maven / Gradle / Ivy

There is a newer version: 2021.12.006
Show newest version
package com.devonfw.cobigen.impl.extension;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.cobigen.api.annotation.Activation;
import com.devonfw.cobigen.api.annotation.ReaderPriority;
import com.devonfw.cobigen.api.exception.CobiGenRuntimeException;
import com.devonfw.cobigen.api.extension.GeneratorPluginActivator;
import com.devonfw.cobigen.api.extension.Merger;
import com.devonfw.cobigen.api.extension.Priority;
import com.devonfw.cobigen.api.extension.TriggerInterpreter;
import com.devonfw.cobigen.impl.aop.ProxyFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.primitives.SignedBytes;

/**
 * The {@link PluginRegistry} manages registrations of {@link Merger}s and {@link TriggerInterpreter}s
 */
public class PluginRegistry {

  /**
   * Currently registered {@link Merger}s mapped by their merge strategy
   */
  private static Map registeredMerger = Maps. newHashMap();

  /** Currently registered {@link TriggerInterpreter}s mapped by their type */
  private static Map registeredTriggerInterpreter = Maps
      . newHashMap();

  /** Currently registered {@link TriggerInterpreter}s mapped by their supporting file extensions */
  private static Multimap registeredTriggerInterpreterByFileExtension = HashMultimap
      . create();

  /** Key-Placeholder for a path representing a folder */
  private static final String FOLDER = "$";

  /** List of registered plugins */
  private static Map, GeneratorPluginActivator> loadedPlugins = new HashMap<>();

  /** Assigning logger to PluginRegistry */
  private static final Logger LOG = LoggerFactory.getLogger(PluginRegistry.class);

  /***
   * Loads the given plug-in and registers all {@link Merger}s and {@link TriggerInterpreter}s bound by the given
   * plug-in
   *
   * @param generatorPlugin plug-in to be loaded
   * @param  Type of the plug-in interface
   * @return the instantiated {@link GeneratorPluginActivator}
   */
  private static  GeneratorPluginActivator loadPlugin(Class generatorPlugin) {

    try {
      Object plugin = generatorPlugin.newInstance();
      LOG.info("Register CobiGen Plug-in '{}'.", generatorPlugin.getCanonicalName());
      if (plugin instanceof GeneratorPluginActivator) {
        // Collect Mergers
        GeneratorPluginActivator activator = (GeneratorPluginActivator) plugin;
        if (activator.bindMerger() != null) {
          for (Merger merger : activator.bindMerger()) {
            registerMerger(merger);
          }
          // adds merger plugins to notifyable list
        }
        // Collect TriggerInterpreters
        if (activator.bindTriggerInterpreter() != null) {
          for (TriggerInterpreter triggerInterpreter : activator.bindTriggerInterpreter()) {
            registerTriggerInterpreter(triggerInterpreter, activator);
          }
        }
        loadedPlugins.put(activator.getClass(), activator);
        return activator;
      } else {
        LOG.warn("Instantiated plugin of class {}, which is not subclass of {}", plugin.getClass().getCanonicalName(),
            GeneratorPluginActivator.class.getCanonicalName());
        return null;
      }
    } catch (InstantiationException | IllegalAccessException e) {
      throw new CobiGenRuntimeException(
          "Could not intantiate CobiGen Plug-in '" + generatorPlugin.getCanonicalName() + "'.", e);
    }
  }

  /**
   * Registers the given {@link Merger}
   *
   * @param merger to be registered
   */
  private static void registerMerger(Merger merger) {

    if (merger == null || StringUtils.isEmpty(merger.getType())) {
      throw new IllegalArgumentException("You cannot register a new Merger with merger==null or type==null or empty!");
    }
    registeredMerger.put(merger.getType(), merger);
    LOG.debug("Merger for type '{}' registered ({}).", merger.getType(), merger.getClass().getCanonicalName());
  }

  /**
   * Registers the given {@link TriggerInterpreter}
   *
   * @param triggerInterpreter to be registered
   * @param plugin the plugin the trigger interpreter is located in
   */
  public static void registerTriggerInterpreter(TriggerInterpreter triggerInterpreter,
      GeneratorPluginActivator plugin) {

    if (triggerInterpreter == null || StringUtils.isEmpty(triggerInterpreter.getType())) {
      throw new IllegalArgumentException(
          "You cannot register a new TriggerInterpreter with triggerInterpreter==null or type==null or empty!");
    }
    registeredTriggerInterpreter.put(triggerInterpreter.getType(), triggerInterpreter);
    Activation annotation = plugin.getClass().getAnnotation(Activation.class);
    if (annotation != null) {
      for (String ext : annotation.byFileExtension()) {
        registeredTriggerInterpreterByFileExtension.put(ext, triggerInterpreter);
      }
      if (annotation.byFolder()) {
        registeredTriggerInterpreterByFileExtension.put(FOLDER, triggerInterpreter);
      }
    }
    LOG.debug("TriggerInterpreter for type '{}' registered ({}).", triggerInterpreter.getType(),
        triggerInterpreter.getClass().getCanonicalName());
  }

  /**
   * Returns the {@link Merger} for the given merge strategy
   *
   * @param mergeStrategy the {@link Merger} should be able to interpret
   * @return the {@link Merger} for the given mergerType or null if there is no {@link Merger} for this
   *         mergerType
   */
  public static Merger getMerger(String mergeStrategy) {

    if (mergeStrategy == null) {
      return null;
    }

    Merger merger = registeredMerger.get(mergeStrategy);
    if (merger == null) {
      LOG.debug("Trying to find merger for type '{}' in {} registered plugins.", mergeStrategy,
          ClassServiceLoader.getGeneratorPluginActivatorClasses().size());
      for (Class activatorClass : ClassServiceLoader
          .getGeneratorPluginActivatorClasses()) {
        LOG.debug("Checking found plug-in activator '{}'", activatorClass);
        if (activatorClass.isAnnotationPresent(Activation.class)) {
          Activation activation = activatorClass.getAnnotation(Activation.class);
          String[] byMergeStrategy = activation.byMergeStrategy();
          if (LOG.isDebugEnabled()) {
            LOG.debug("Plug-in will be activated by merge strategies '{}'.",
                Arrays.stream(byMergeStrategy).collect(Collectors.joining(",")));
          }
          Arrays.sort(byMergeStrategy);
          if (Arrays.binarySearch(byMergeStrategy, mergeStrategy) >= 0) {
            loadPlugin(activatorClass);
            break;
          } else {
            LOG.debug("Merge strategy not found. Skipping.");
          }
        } else {
          LOG.debug("Activator annotation not present. Skipping.");
        }
      }
      merger = registeredMerger.get(mergeStrategy);
    }
    if (merger != null) {
      merger = ProxyFactory.getProxy(merger);
    }
    return merger;
  }

  /**
   * Returns the {@link TriggerInterpreter} for the given triggerType
   *
   * @param triggerType the {@link TriggerInterpreter} should be able to interpret
   * @return the {@link TriggerInterpreter} for the given triggerType of null if there is no
   */
  public static TriggerInterpreter getTriggerInterpreter(String triggerType) {

    if (triggerType == null) {
      return null;
    }

    TriggerInterpreter triggerInterpreter = registeredTriggerInterpreter.get(triggerType);
    if (triggerInterpreter != null) {
      triggerInterpreter = ProxyFactory.getProxy(triggerInterpreter);
    }
    return triggerInterpreter;
  }

  /**
   * Returns a {@link Map} of all {@link TriggerInterpreter} keys.
   *
   * @param inputPath the path of the input to be read to just return the valid {@link TriggerInterpreter}s in order
   *        sorted by {@link Priority}
   *
   * @return all {@link TriggerInterpreter} keys as a set of strings.
   */
  public static List getTriggerInterpreters(Path inputPath) {

    String extension;
    if (inputPath.toFile().isFile()) {
      extension = FilenameUtils.getExtension(inputPath.getFileName().toString());
      LOG.debug("Trying to find trigger interpreter by file extension '{}'", extension);
      for (Class activatorClass : ClassServiceLoader
          .getGeneratorPluginActivatorClasses()) {
        LOG.debug("Checking found plug-in activator '{}'", activatorClass);
        if (activatorClass.isAnnotationPresent(Activation.class)) {
          Activation activation = activatorClass.getAnnotation(Activation.class);
          String[] byFileExtension = activation.byFileExtension();
          if (LOG.isDebugEnabled()) {
            LOG.debug("Plug-in will be activated by file extensions '{}'.",
                Arrays.stream(byFileExtension).collect(Collectors.joining(",")));
          }
          Arrays.sort(byFileExtension);
          if (Arrays.binarySearch(byFileExtension, extension) >= 0 && !loadedPlugins.containsKey(activatorClass)) {
            loadPlugin(activatorClass);
          } else {
            LOG.debug("File extension not found. Skipping.");
          }
        } else {
          LOG.debug("Activator annotation not present. Skipping.");
        }
      }
    } else { // directory
      extension = FOLDER;
      LOG.debug("Trying to find trigger interpreter by for folder inputs");
      for (Class activatorClass : ClassServiceLoader
          .getGeneratorPluginActivatorClasses()) {
        LOG.debug("Checking found plug-in activator '{}'", activatorClass);
        if (activatorClass.isAnnotationPresent(Activation.class)) {
          Activation activation = activatorClass.getAnnotation(Activation.class);
          if (activation.byFolder() && !loadedPlugins.containsKey(activatorClass)) {
            loadPlugin(activatorClass);
          }
        } else {
          LOG.debug("Activator annotation not present. Skipping.");
        }
      }
    }

    List sortedPlugins = registeredTriggerInterpreterByFileExtension.get(extension).stream()
        .sorted((a, b) -> {
          Priority priorityA = getPriority(a.getClass());
          Priority priorityB = getPriority(b.getClass());
          return SignedBytes.compare(priorityA.getRank(), priorityB.getRank());
        }).collect(Collectors.toList());

    return sortedPlugins;
  }

  /**
   * Extracts the {@link ReaderPriority} of a trigger interpreter
   *
   * @param clazz class to get the {@link ReaderPriority} annotation from (commonly the TriggerInterpreter classes)
   * @return the priority of the input reader
   */
  private static Priority getPriority(Class clazz) {

    Priority priority;
    if (clazz.getClass().isAnnotationPresent(ReaderPriority.class)) {
      ReaderPriority[] annotation = clazz.getClass().getAnnotationsByType(ReaderPriority.class);
      priority = annotation[0].value();
    } else {
      try {
        priority = (Priority) ReaderPriority.class.getMethod("value").getDefaultValue();
      } catch (NoSuchMethodException | SecurityException e) {
        LOG.error(
            "Could not find value() method of ReaderPriority. This should be an invalid case. Setting priority to hardcoded LOW to proceed. Please anyhow report a bug please.");
        priority = Priority.LOW;
      }
    }
    return priority;
  }

  /**
   * Notifies plug-ins about the new template root path
   *
   * @param configFolder Path to update on registered plug-ins
   */
  public static void notifyPlugins(Path configFolder) {

    for (Object plugin : loadedPlugins.values()) {
      ((GeneratorPluginActivator) plugin).setProjectRoot(configFolder);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy