From be7c53323688109a0f4fa813fadb769a0a6eaea5 Mon Sep 17 00:00:00 2001 From: mischuma Date: Fri, 31 Jul 2020 14:29:40 +0200 Subject: [PATCH 01/14] #1207 Set next development version --- cobigen/cobigen-core-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobigen/cobigen-core-parent/pom.xml b/cobigen/cobigen-core-parent/pom.xml index 8405c5f3db..929f1f0ce2 100644 --- a/cobigen/cobigen-core-parent/pom.xml +++ b/cobigen/cobigen-core-parent/pom.xml @@ -12,7 +12,7 @@ - 6.1.2 + 6.2.0-SNAPSHOT From 1078dd0569016059b4b9107c83fd770bcdcda19f Mon Sep 17 00:00:00 2001 From: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com> Date: Wed, 12 Aug 2020 10:36:19 +0200 Subject: [PATCH 02/14] Template root path notifier (required for xml_plugin) (#1204) * added new notifyPlugins method which notifies plugins when the path to the root template folder was changed added new MERGE_SCHEMA_RESOURCE_FOLDER constant to api added new setProjectRoot method to GeneratorPluginActivator interface (used to update template root location on plugins) made sure to send an update request to all plugins when the path to the template root was changed * set setProjectRoot method in interface to default throws a NotYetSupportedException with a hint about what still could be implemented * replaced Google Lists with native ArrayList removed type check in notifyPlugins method --- .../api/constants/ConfigurationConstants.java | 3 ++ .../extension/GeneratorPluginActivator.java | 15 ++++++++++ .../devonfw/cobigen/impl/CobiGenFactory.java | 3 ++ .../impl/config/ConfigurationHolder.java | 3 ++ .../impl/extension/PluginRegistry.java | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+) diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/ConfigurationConstants.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/ConfigurationConstants.java index ee7ba3ca28..8badac4055 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/ConfigurationConstants.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/constants/ConfigurationConstants.java @@ -28,6 +28,9 @@ public class ConfigurationConstants { /** Resource folder containing templates */ public static final String TEMPLATE_RESOURCE_FOLDER = "src/main/templates"; + /** Resource folder containing merge schemas */ + public static final String MERGE_SCHEMA_RESOURCE_FOLDER = "src/main/resources/mergeSchemas"; + /** Delimiter splitting the template folder and value of references in templates.xml files */ public static final String REFERENCE_DELIMITER = "::"; } diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java index 3f5530d013..ae4a79f572 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java @@ -1,8 +1,10 @@ package com.devonfw.cobigen.api.extension; +import java.nio.file.Path; import java.util.List; import com.devonfw.cobigen.api.annotation.ExceptionFacade; +import com.devonfw.cobigen.api.exception.NotYetSupportedException; /** * This interface should be inherited for all plug-ins to extend the generators logic by additional @@ -18,6 +20,19 @@ public interface GeneratorPluginActivator { */ public List bindMerger(); + /** + * This function should be called by a plugin if the path to the template root was changed + * @param path + * Path to project root folder + * + * @throws NotYetSupportedException + * if not implemented yet + */ + default void setProjectRoot(Path path) throws NotYetSupportedException { + throw new NotYetSupportedException( + "Method should pass the path " + path + " of the new template root to the plugin"); + } + /** * This function should return all {@link TriggerInterpreter} implementations, which should be provided by * this plug-in implementation diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java index 625481229e..8eb12321c7 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java @@ -12,6 +12,7 @@ import com.devonfw.cobigen.impl.aop.ProxyFactory; import com.devonfw.cobigen.impl.config.ConfigurationHolder; import com.devonfw.cobigen.impl.config.ContextConfiguration; +import com.devonfw.cobigen.impl.extension.PluginRegistry; import com.devonfw.cobigen.impl.extension.ServiceLookup; import com.devonfw.cobigen.impl.healthcheck.HealthCheckImpl; import com.devonfw.cobigen.impl.util.FileSystemUtil; @@ -45,6 +46,8 @@ public static CobiGen create(URI configFileOrFolder) throws InvalidConfiguration BeanFactory beanFactory = new BeanFactory(); beanFactory.addManuallyInitializedBean(configurationHolder); CobiGen createBean = beanFactory.createBean(CobiGen.class); + // Notifies all plugins of new template root path + PluginRegistry.notifyPlugins(configFolder); return createBean; } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/config/ConfigurationHolder.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/config/ConfigurationHolder.java index 1afbb59528..0324e400d9 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/config/ConfigurationHolder.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/config/ConfigurationHolder.java @@ -6,6 +6,7 @@ import com.devonfw.cobigen.api.exception.InvalidConfigurationException; import com.devonfw.cobigen.impl.config.entity.Trigger; +import com.devonfw.cobigen.impl.extension.PluginRegistry; import com.google.common.collect.Maps; /** @@ -29,6 +30,8 @@ public class ConfigurationHolder { */ public ConfigurationHolder(Path configurationPath) { this.configurationPath = configurationPath; + // updates the root template path and informs all of its observers + PluginRegistry.notifyPlugins(configurationPath); } /** diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index 7e5d5d6ab6..13c1da1007 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -1,7 +1,10 @@ package com.devonfw.cobigen.impl.extension; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -10,6 +13,7 @@ import org.slf4j.LoggerFactory; import com.devonfw.cobigen.api.exception.CobiGenRuntimeException; +import com.devonfw.cobigen.api.exception.NotYetSupportedException; import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.Merger; import com.devonfw.cobigen.api.extension.TriggerInterpreter; @@ -33,6 +37,11 @@ public class PluginRegistry { private static Map registeredTriggerInterpreter = Collections.synchronizedMap(Maps. newHashMap()); + /** + * List of registered plugins + */ + private static List pluginsList = new ArrayList<>(); + /** * Assigning logger to PluginRegistry */ @@ -58,6 +67,8 @@ public static void loadPlugin(Class gene for (Merger merger : ((GeneratorPluginActivator) plugin).bindMerger()) { PluginRegistry.registerMerger(merger); } + // adds merger plugins to notifyable list + pluginsList.add(plugin); } // Collect ITriggerInterpreter if (((GeneratorPluginActivator) plugin).bindTriggerInterpreter() != null) { @@ -154,4 +165,21 @@ public static Set getTriggerInterpreterKeySet() { return new HashSet<>(registeredTriggerInterpreter.keySet()); } + /** + * Notifies plugins about the new template root path + * + * @param configFolder + * Path to update on registered plugins + */ + public static void notifyPlugins(Path configFolder) { + + for (Object plugin : pluginsList) { + try { + ((GeneratorPluginActivator) plugin).setProjectRoot(configFolder); + } catch (NotYetSupportedException e) { + LOG.debug("setProjectRoot() method is not implemented in this plugin yet!", e); + } + } + } + } From 6f71730154a83f0ab075d0a34650e69f5d078178 Mon Sep 17 00:00:00 2001 From: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com> Date: Wed, 19 Aug 2020 23:03:55 +0200 Subject: [PATCH 03/14] Dev core inputreader priorization (#1210) * added new containsException and notContainsException methods to GenerationReportToAssert * fixed #1208 added changes for #1209 changed isMostLikelyReadable return type from boolean to Boolean added a simple cache for isMostLikelyReadable iterations * changed return type of notContainsException from boolean to GenerationReportToAssert added list of reported errors to the thrown AssertionError in containsException * declared Hashmap as a Map made sure to return a valid external inputreader, while building the readableCache moved redundant code to new getValidInputReader method * made sure to call isMostLikelyReadable at least once renamed keySet to triggerInterpreterKeySet passed result of isMostLikelyReadable check to getValidInputReader * added new isMostLikelyReadable method which fills the cache optimized and renamed getValidInputReader method --- .../devonfw/cobigen/api/InputInterpreter.java | 12 ++- .../cobigen/api/extension/InputReader.java | 2 +- .../assertj/GenerationReportToAssert.java | 39 ++++++++ .../cobigen/impl/generator/CobiGenImpl.java | 2 +- .../impl/generator/InputInterpreterImpl.java | 92 ++++++++++++++++--- 5 files changed, 129 insertions(+), 18 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java index fbde15a7c5..a7d8d24eff 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java @@ -29,7 +29,7 @@ public interface InputInterpreter { public List resolveContainers(Object input); /** - * Reads the content at a path via a fitting {@link InputReader} and returns a CobiGen compliant input + * Reads the content at a path via a fitting {@link InputReader} and returns a CobiGen compliant input. * @param type * of the input to be read. Is used to resolve a fitting InputReader * @param path @@ -49,7 +49,8 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit /** * Reads the content at a path via the first fitting {@link InputReader} and returns a CobiGen compliant - * input + * input. Supports at least one EXTERNAL input reader with the same isMostLikelyReadable criteria than an + * INTERNAL input reader. * @param path * the {@link Path} to the object. Can also point to a folder * @param inputCharset @@ -72,9 +73,12 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit * the file {@link Path}. * @param type * of the input to be read. Is used to resolve a fitting InputReader - * @return true, if it is most likely, that the file can be read by this input reader's read method
+ * @return true, if it is most likely, that the file can be read by this external input reader's read + * method
+ * null, if it is most likely, that the file can be read by this internal input reader's read + * method
* false, if the reader is most likely not able to read the file. */ - public boolean isMostLikelyReadable(String type, Path path); + public Boolean isMostLikelyReadable(String type, Path path); } \ No newline at end of file diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java index d66854b284..1aa4069cc2 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java @@ -93,7 +93,7 @@ public interface InputReader { * false, if the reader is most likely not able to read the file. */ @SuppressWarnings("unused") - default public boolean isMostLikelyReadable(Path path) { + default public Boolean isMostLikelyReadable(Path path) { return false; } } diff --git a/cobigen/cobigen-core-parent/cobigen-core-test/src/main/java/com/devonfw/cobigen/test/assertj/GenerationReportToAssert.java b/cobigen/cobigen-core-parent/cobigen-core-test/src/main/java/com/devonfw/cobigen/test/assertj/GenerationReportToAssert.java index f41529373d..862bd923d6 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-test/src/main/java/com/devonfw/cobigen/test/assertj/GenerationReportToAssert.java +++ b/cobigen/cobigen-core-parent/cobigen-core-test/src/main/java/com/devonfw/cobigen/test/assertj/GenerationReportToAssert.java @@ -43,4 +43,43 @@ public GenerationReportToAssert isSuccessful() { } return this; } + + /** + * Checks if an Exception of the given class was found in the {@link GenerationReportTo} error reports. + * + * @param exception + * Class of Exception to check for + * @return {@link GenerationReportToAssert} + * @throws AssertionError + * if another or no Exception was found in the report + */ + public GenerationReportToAssert containsException(Class exception) throws AssertionError { + + for (Throwable error : actual.getErrors()) { + if (error.getClass().equals(exception)) { + return this; + } + } + throw new AssertionError("Expected an exception of class " + exception + + ", which could not be found in the list " + actual.getErrors()); + } + + /** + * Checks if an Exception of the given class was NOT found in the {@link GenerationReportTo} error + * reports. errors. + * + * @param exception + * Class of Exception to check for + * @return boolean + * @throws AssertionError + * if the Exception was found in the report + */ + public GenerationReportToAssert notContainsException(Class exception) throws AssertionError { + for (Throwable error : actual.getErrors()) { + if (error.getClass().equals(exception)) { + throw new AssertionError("Expected no Exception of class: " + exception + " in error reports."); + } + } + return this; + } } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java index f623af6de5..789e418fe8 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java @@ -165,7 +165,7 @@ public Object read(Path path, Charset inputCharset, Object... additionalArgument } @Override - public boolean isMostLikelyReadable(String type, Path path) { + public Boolean isMostLikelyReadable(String type, Path path) { return inputInterpreter.isMostLikelyReadable(type, path); } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java index 8b7d0ef469..e8c3cafab2 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java @@ -3,7 +3,9 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import javax.inject.Inject; @@ -66,25 +68,91 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit @Override public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException { - Set keySet = PluginRegistry.getTriggerInterpreterKeySet(); + Set triggerInterpreterKeySet = PluginRegistry.getTriggerInterpreterKeySet(); // We first try to find an input reader that is most likely readable - for (String s : keySet) { - try { - if (isMostLikelyReadable(s, path)) { - LOG.debug("Try reading input {} with inputreader '{}'...", path, s); - return getInputReader(s).read(path, inputCharset, additionalArguments); - } - } catch (InputReaderException e) { - LOG.debug( - "Was not able to read input {} with inputreader '{}' although it was reported to be most likely readable. Trying next input reader...", - path, s, e); + Map readableCache = new HashMap<>(); + Object readable = null; + + // Create cache for readable states + for (String triggerType : triggerInterpreterKeySet) { + readable = readInput(path, inputCharset, readableCache, triggerType, true, additionalArguments); + if (readable != null) { + return readable; } } + + // If no external input reader was found, check internal input readers + for (String triggerType : readableCache.keySet()) { + readable = readInput(path, inputCharset, readableCache, triggerType, null, additionalArguments); + if (readable != null) { + return readable; + } + } + throw new InputReaderException("Could not read input at path " + path + " with any installed plugin."); + + } + + /** + * Checks and returns a valid input either directly or from a provided cache. + * + * @param path + * the Path to the object. Can also point to a folder + * @param inputCharset + * of the input to be used + * @param readableCache + * HashMap of TriggerInterpreter and Boolean + * @param triggerType + * type of TriggerInterpreter + * @param expectedResult + * Boolean expected result of isMostLikelyReadable check + * @param additionalArguments + * depending on the InputReader implementation + * @return Object that is a valid input or null if the file cannot be read by any InputReader + */ + private Object readInput(Path path, Charset inputCharset, Map readableCache, String triggerType, + Boolean expectedResult, Object... additionalArguments) { + String readerType = "EXTERNAL"; + if (expectedResult == null) { + readerType = "INTERNAL"; + } + try { + if (isMostLikelyReadable(triggerType, path, readableCache) == expectedResult) { + LOG.info("Try reading input {} with {} inputreader '{}'...", path, readerType, triggerType); + return getInputReader(triggerType).read(path, inputCharset, additionalArguments); + } + } catch (InputReaderException e) { + LOG.debug( + "Was not able to read input {} with {} inputreader '{}' although it was reported to be most likely readable. Trying next input reader...", + path, readerType, triggerType, e); + } catch (Throwable e) { + LOG.debug( + "While reading the input {} with the {} inputreader {}, an Exception occured. Trying next input reader...", + path, readerType, triggerType, e); + } + return null; + } + + /** + * Checks if the input is most likely readable and fills the provided cache + * + * @param type + * String of TriggerType + * @param path + * the file Path + * @param cache + * Map of TriggerType and isMostLikelyReadable check results + * @return Boolean true if readable by external plugin, null for internal plugin and false if not readable + */ + private Boolean isMostLikelyReadable(String type, Path path, Map cache) { + if (!cache.containsKey(type)) { + cache.put(type, isMostLikelyReadable(type, path)); + } + return cache.get(type); } @Override - public boolean isMostLikelyReadable(String type, Path path) { + public Boolean isMostLikelyReadable(String type, Path path) { return getInputReader(type).isMostLikelyReadable(path); } From 390ddc6379aa88c63cf97a538e7c7a94cd6f3fb7 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Tue, 8 Sep 2020 13:44:45 +0200 Subject: [PATCH 04/14] #1209 allow prioritization based on annotation as overloading of Boolean and boolean return types does not work as they are not covariant --- .../api/annotation/ReaderPriority.java | 21 ++++++++ .../extension/GeneratorPluginActivator.java | 5 +- .../cobigen/api/extension/InputReader.java | 12 +++-- .../cobigen/api/extension/Priority.java | 36 +++++++++++++ .../impl/extension/PluginRegistry.java | 51 ++++++++++++++----- .../ExternalProcessHandler.java | 3 +- .../impl/generator/InputInterpreterImpl.java | 8 +-- 7 files changed, 110 insertions(+), 26 deletions(-) create mode 100644 cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java create mode 100644 cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/Priority.java diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java new file mode 100644 index 0000000000..a0ef7ffaf8 --- /dev/null +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java @@ -0,0 +1,21 @@ +package com.devonfw.cobigen.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.devonfw.cobigen.api.extension.Priority; + +/** + * The priority to take into account when try reading an input. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +@Inherited +public @interface ReaderPriority { + + /** The input readers priority */ + Priority value(); +} diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java index ae4a79f572..daed4f151b 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/GeneratorPluginActivator.java @@ -28,9 +28,8 @@ public interface GeneratorPluginActivator { * @throws NotYetSupportedException * if not implemented yet */ - default void setProjectRoot(Path path) throws NotYetSupportedException { - throw new NotYetSupportedException( - "Method should pass the path " + path + " of the new template root to the plugin"); + default void setProjectRoot(@SuppressWarnings("unused") Path path) { + // do nothing } /** diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java index 1aa4069cc2..5a4f0b7589 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/InputReader.java @@ -5,6 +5,9 @@ import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.devonfw.cobigen.api.annotation.ExceptionFacade; import com.devonfw.cobigen.api.exception.InputReaderException; @@ -16,6 +19,9 @@ @ExceptionFacade public interface InputReader { + /** Logger instance. */ + public static final Logger LOG = LoggerFactory.getLogger(InputReader.class); + /** * This function will be called if matching triggers or matching templates should be retrieved for a given * input object @@ -92,8 +98,6 @@ public interface InputReader { * @return true, if it is most likely, that the file can be read by this input reader's read method
* false, if the reader is most likely not able to read the file. */ - @SuppressWarnings("unused") - default public Boolean isMostLikelyReadable(Path path) { - return false; - } + public boolean isMostLikelyReadable(Path path); + } diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/Priority.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/Priority.java new file mode 100644 index 0000000000..85a9713651 --- /dev/null +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/Priority.java @@ -0,0 +1,36 @@ +package com.devonfw.cobigen.api.extension; + +/** + * Priorities an input reader is ranked with in case + * {@link InputReader#isMostLikelyReadable(java.nio.file.Path)} is returning true for multiple plug-ins + * available. + */ +public enum Priority { + + /** Standard devonfw plug-ins + meta-language readers (i.e. XML) */ + LOW((byte) 3), + + /** For example language specific readers like specific XML languages */ + MEDIUM((byte) 2), + + /** Highest priority for custom use cases */ + HIGH((byte) 1); + + /** The rank */ + private byte rank; + + /** + * @param rank + * the rank + */ + private Priority(byte rank) { + this.rank = rank; + } + + /** + * @return the rank for sorting the priorities + */ + public byte getRank() { + return rank; + } +} diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index 13c1da1007..a55c69d2d8 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -3,22 +3,23 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.devonfw.cobigen.api.annotation.ReaderPriority; import com.devonfw.cobigen.api.exception.CobiGenRuntimeException; -import com.devonfw.cobigen.api.exception.NotYetSupportedException; 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.Maps; +import com.google.common.primitives.SignedBytes; /** * The {@link PluginRegistry} manages registrations of {@link Merger}s and {@link TriggerInterpreter}s @@ -62,7 +63,7 @@ public static void loadPlugin(Class gene Object plugin = generatorPlugin.newInstance(); LOG.info("Register CobiGen Plug-in '{}'.", generatorPlugin.getCanonicalName()); if (plugin instanceof GeneratorPluginActivator) { - // Collect IMerger + // Collect Mergers if (((GeneratorPluginActivator) plugin).bindMerger() != null) { for (Merger merger : ((GeneratorPluginActivator) plugin).bindMerger()) { PluginRegistry.registerMerger(merger); @@ -70,7 +71,7 @@ public static void loadPlugin(Class gene // adds merger plugins to notifyable list pluginsList.add(plugin); } - // Collect ITriggerInterpreter + // Collect TriggerInterpreters if (((GeneratorPluginActivator) plugin).bindTriggerInterpreter() != null) { for (TriggerInterpreter triggerInterpreter : ((GeneratorPluginActivator) plugin) .bindTriggerInterpreter()) { @@ -161,24 +162,46 @@ public static TriggerInterpreter getTriggerInterpreter(String triggerType) { * * @return all {@link TriggerInterpreter} keys as a set of strings. */ - public static Set getTriggerInterpreterKeySet() { - return new HashSet<>(registeredTriggerInterpreter.keySet()); + public static List getTriggerInterpreterKeySet() { + return registeredTriggerInterpreter.entrySet().stream().sorted((a, b) -> { + Priority priorityA = getPriority(a.getValue()); + Priority priorityB = getPriority(b.getValue()); + return SignedBytes.compare(priorityA.getRank(), priorityB.getRank()); + }).map(e -> e.getKey()).collect(Collectors.toList()); } /** - * Notifies plugins about the new template root path + * @param triggerInterpreter + * {@link TriggerInterpreter} + * @return the priority of the input reader + */ + private static Priority getPriority(TriggerInterpreter triggerInterpreter) { + Priority priority; + if (triggerInterpreter.getClass().isAnnotationPresent(ReaderPriority.class)) { + ReaderPriority[] annotation = triggerInterpreter.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 plugins + * Path to update on registered plug-ins */ public static void notifyPlugins(Path configFolder) { for (Object plugin : pluginsList) { - try { - ((GeneratorPluginActivator) plugin).setProjectRoot(configFolder); - } catch (NotYetSupportedException e) { - LOG.debug("setProjectRoot() method is not implemented in this plugin yet!", e); - } + ((GeneratorPluginActivator) plugin).setProjectRoot(configFolder); } } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/externalprocess/ExternalProcessHandler.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/externalprocess/ExternalProcessHandler.java index 96cdd62a77..33be9033a2 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/externalprocess/ExternalProcessHandler.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/externalprocess/ExternalProcessHandler.java @@ -503,7 +503,8 @@ public boolean isNotConnected() { if (response.equals("true")) { LOG.warn( - "An old version is currently deployed. Please consider deploying the newest version to get the current bug fixes/features"); + "The old version {} of {} is currently deployed. Please consider deploying the newest version to get the current bug fixes/features.", + processProperties.getServerVersion(), exeName); return false; } } catch (Exception e) { diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java index e8c3cafab2..9122b7f57e 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java @@ -6,7 +6,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import javax.inject.Inject; @@ -68,12 +67,13 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit @Override public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException { - Set triggerInterpreterKeySet = PluginRegistry.getTriggerInterpreterKeySet(); + List triggerInterpreterKeySet = PluginRegistry.getTriggerInterpreterKeySet(); + // We first try to find an input reader that is most likely readable Map readableCache = new HashMap<>(); Object readable = null; - // Create cache for readable states + // First find plug-ins which might come from outside of cobigen space (externally developed plugins) for (String triggerType : triggerInterpreterKeySet) { readable = readInput(path, inputCharset, readableCache, triggerType, true, additionalArguments); if (readable != null) { @@ -127,7 +127,7 @@ private Object readInput(Path path, Charset inputCharset, Map r path, readerType, triggerType, e); } catch (Throwable e) { LOG.debug( - "While reading the input {} with the {} inputreader {}, an Exception occured. Trying next input reader...", + "While reading the input {} with the {} inputreader {}, an exception occured. Trying next input reader...", path, readerType, triggerType, e); } return null; From fad82d197d518ca6f15975c2e7c2610dc19c7544 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Wed, 9 Sep 2020 16:30:02 +0200 Subject: [PATCH 05/14] #1228 lazy loading annotation processing for all plugins --- .../devonfw/cobigen/api/InputInterpreter.java | 18 --- .../cobigen/api/annotation/Activation.java | 30 ++++ .../devonfw/cobigen/api/annotation/Name.java | 19 +++ .../api/annotation/ReaderPriority.java | 6 +- .../api/extension/TextTemplateEngine.java | 6 - .../cobigen-core-systemtest/pom.xml | 2 +- .../cobigen/systemtest/ClassLoadingTest.java | 4 +- .../systemtest/ContainerMatcherTest.java | 13 +- .../cobigen/systemtest/GenerationTest.java | 7 +- .../systemtest/TriggerActivationTest.java | 26 ++-- .../systemtest/util/PluginMockFactory.java | 9 +- .../devonfw/cobigen/impl/CobiGenFactory.java | 5 - .../impl/extension/ClassServiceLoader.java | 86 +++++++++++ .../impl/extension/PluginRegistry.java | 133 +++++++++++++----- .../cobigen/impl/extension/ServiceLookup.java | 50 ------- .../extension/TemplateEngineRegistry.java | 30 +++- .../cobigen/impl/generator/CobiGenImpl.java | 5 - .../impl/generator/InputInterpreterImpl.java | 61 +++----- .../cobigen/stubs/TemplateEngineStub.java | 7 +- .../config/common/AbstractUnitTest.java | 2 +- 20 files changed, 323 insertions(+), 196 deletions(-) create mode 100644 cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java create mode 100644 cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Name.java create mode 100644 cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java delete mode 100644 cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ServiceLookup.java diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java index a7d8d24eff..7c2183f830 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java @@ -63,22 +63,4 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit */ public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException; - /** - * Try to determine, whether the input file is readable by the plug-ins parser. If false is returned by - * the method, it is not assured, that the read method is not called at all. In case of the situation, - * that no plug-in is most likely to read the input, CobiGen will try every input reader to read the - * input. Please be aware, that this method should be kept highly performant. It does not have to be an - * exact result. - * @param path - * the file {@link Path}. - * @param type - * of the input to be read. Is used to resolve a fitting InputReader - * @return true, if it is most likely, that the file can be read by this external input reader's read - * method
- * null, if it is most likely, that the file can be read by this internal input reader's read - * method
- * false, if the reader is most likely not able to read the file. - */ - public Boolean isMostLikelyReadable(String type, Path path); - } \ No newline at end of file diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java new file mode 100644 index 0000000000..16845ede53 --- /dev/null +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java @@ -0,0 +1,30 @@ +package com.devonfw.cobigen.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation allowing activation criteria for a plug-in + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +@Inherited +public @interface Activation { + + /** + * @return the file extensions for which the plug-in should be activated respectively for which the + * plug-in provides an input reader. The file extension should be noted by the extension only + * without dot or asterix, i.e. { 'html', 'xhtml' } + */ + String[] byFileExtension() default {}; + + /** + * @return the merge strategies provided by this plug-in, which will cause the plug-in lazily to be loaded + * just in case a merge strategy is requested which is provided by this plug-in + */ + String[] byMergeStrategy() default {}; + +} diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Name.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Name.java new file mode 100644 index 0000000000..6823bacb50 --- /dev/null +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Name.java @@ -0,0 +1,19 @@ +package com.devonfw.cobigen.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Name of an extension + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +@Inherited +public @interface Name { + + /** Name of the plug-in */ + String value(); +} diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java index a0ef7ffaf8..a4395653a6 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/ReaderPriority.java @@ -7,9 +7,11 @@ import java.lang.annotation.Target; import com.devonfw.cobigen.api.extension.Priority; +import com.devonfw.cobigen.api.extension.TriggerInterpreter; /** - * The priority to take into account when try reading an input. + * The priority to take into account when try reading an input. This annotation is meant to be set on + * {@link TriggerInterpreter} classes */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @@ -17,5 +19,5 @@ public @interface ReaderPriority { /** The input readers priority */ - Priority value(); + Priority value() default Priority.LOW; } diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/TextTemplateEngine.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/TextTemplateEngine.java index 5a5c015df7..b2bf571c7c 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/TextTemplateEngine.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/extension/TextTemplateEngine.java @@ -13,12 +13,6 @@ @ExceptionFacade public interface TextTemplateEngine { - /** - * Returns the identifying name of the template engine. - * @return name of the template engine - */ - public String getName(); - /** * The return value is considered for automatically retrieving file names from templates within a * template-scan. The template file ending will be eliminated (if exists) from a template's file name to diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml b/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml index 81f5e9ea50..25d8f9eaa9 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml @@ -26,7 +26,7 @@ ${project.groupId} tempeng-freemarker - 2.0.0 + 2.2.0-SNAPSHOT test diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ClassLoadingTest.java b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ClassLoadingTest.java index feb8ed0ff5..81b1af9556 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ClassLoadingTest.java +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ClassLoadingTest.java @@ -21,6 +21,7 @@ import org.junit.Test; import com.devonfw.cobigen.api.CobiGen; +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.MatcherInterpreter; import com.devonfw.cobigen.api.extension.TriggerInterpreter; @@ -134,6 +135,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -164,7 +166,7 @@ public String toString() { .thenReturn(ImmutableMap. builder().put("rootPackage", "com.devonfw") .put("entityName", "Test").build()); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); return container; } diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ContainerMatcherTest.java b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ContainerMatcherTest.java index d72cb43c62..3fe41a8e7e 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ContainerMatcherTest.java +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/ContainerMatcherTest.java @@ -21,10 +21,8 @@ import org.junit.Assert; import org.junit.Test; -import com.devonfw.cobigen.systemtest.common.AbstractApiTest; -import com.devonfw.cobigen.test.matchers.MatcherToMatcher; -import com.devonfw.cobigen.test.matchers.VariableAssignmentToMatcher; import com.devonfw.cobigen.api.CobiGen; +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.MatcherInterpreter; import com.devonfw.cobigen.api.extension.TriggerInterpreter; @@ -34,6 +32,9 @@ import com.devonfw.cobigen.impl.CobiGenFactory; import com.devonfw.cobigen.impl.config.entity.ContainerMatcher; import com.devonfw.cobigen.impl.extension.PluginRegistry; +import com.devonfw.cobigen.systemtest.common.AbstractApiTest; +import com.devonfw.cobigen.test.matchers.MatcherToMatcher; +import com.devonfw.cobigen.test.matchers.VariableAssignmentToMatcher; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -211,6 +212,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -242,7 +244,7 @@ public String toString() { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(child2))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // create CobiGen instance File templatesFolder = new File(testFileRootPath + "selectiveContainerGeneration"); @@ -303,6 +305,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -345,7 +348,7 @@ public String toString() { .thenReturn(ImmutableMap. builder().put("rootPackage", "com.devonfw") .put("entityName", "Test").build()); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); return container; } diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/GenerationTest.java b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/GenerationTest.java index ab906959b6..d3c73ec2a5 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/GenerationTest.java +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/GenerationTest.java @@ -26,6 +26,7 @@ import com.devonfw.cobigen.api.CobiGen; import com.devonfw.cobigen.api.exception.InvalidConfigurationException; +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.MatcherInterpreter; import com.devonfw.cobigen.api.extension.TriggerInterpreter; @@ -150,6 +151,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -166,7 +168,7 @@ public String toString() { variables.put("contextVar", "contextValue"); when(matcher.resolveVariables(any(MatcherTo.class), any(List.class))).thenReturn(variables); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // further setup File folder = tmpFolder.newFolder(); @@ -200,6 +202,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -216,7 +219,7 @@ public String toString() { variables.put("contextVar", "contextValue"); when(matcher.resolveVariables(any(MatcherTo.class), any(List.class))).thenReturn(variables); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // further setup File folder = tmpFolder.newFolder(); diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/TriggerActivationTest.java b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/TriggerActivationTest.java index bda9bda359..294e52b6e6 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/TriggerActivationTest.java +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/TriggerActivationTest.java @@ -16,14 +16,15 @@ import org.junit.Test; -import com.devonfw.cobigen.systemtest.common.AbstractApiTest; -import com.devonfw.cobigen.test.matchers.MatcherToMatcher; import com.devonfw.cobigen.api.CobiGen; +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.MatcherInterpreter; import com.devonfw.cobigen.api.extension.TriggerInterpreter; import com.devonfw.cobigen.impl.CobiGenFactory; import com.devonfw.cobigen.impl.extension.PluginRegistry; +import com.devonfw.cobigen.systemtest.common.AbstractApiTest; +import com.devonfw.cobigen.test.matchers.MatcherToMatcher; /** * Test suite, which tests activation of triggers due to matcher accumulation types. @@ -49,6 +50,7 @@ public void testNoActivation_1Of2AND_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -66,7 +68,7 @@ public void testNoActivation_1Of2AND_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -89,6 +91,7 @@ public void testNoActivation_1Of2AND_1OR_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -106,7 +109,7 @@ public void testNoActivation_1Of2AND_1OR_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -128,6 +131,7 @@ public void testActivation_2Of2AND_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -145,7 +149,7 @@ public void testActivation_2Of2AND_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -168,6 +172,7 @@ public void testNoActivation_2Of2AND_1NOT_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -184,7 +189,7 @@ public void testNoActivation_2Of2AND_1NOT_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("or"), ANY, sameInstance(input))))).thenReturn(false); when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))).thenReturn(true); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -206,6 +211,7 @@ public void testNoActivation_1OR_0AND_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -223,7 +229,7 @@ public void testNoActivation_1OR_0AND_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -245,6 +251,7 @@ public void testNoActivation_1OR_1NOT_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -261,7 +268,7 @@ public void testNoActivation_1OR_1NOT_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("or"), ANY, sameInstance(input))))).thenReturn(true); when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))).thenReturn(true); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); @@ -283,6 +290,7 @@ public void testActivation_1OR_MatcherMatches() throws Exception { Object input = new Object(); // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -296,7 +304,7 @@ public void testActivation_1OR_MatcherMatches() throws Exception { when(matcher.matches(argThat(new MatcherToMatcher(equalTo("not"), ANY, sameInstance(input))))) .thenReturn(false); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); // execution CobiGen cobigen = CobiGenFactory.create(new File(testFileRootPath + "templates").toURI()); diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/util/PluginMockFactory.java b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/util/PluginMockFactory.java index 7104d934ae..786184c9be 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/util/PluginMockFactory.java +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/src/test/java/com/devonfw/cobigen/systemtest/util/PluginMockFactory.java @@ -11,15 +11,17 @@ import java.util.HashMap; -import com.devonfw.cobigen.test.matchers.MatcherToMatcher; -import com.devonfw.cobigen.test.matchers.VariableAssignmentToMatcher; +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.MatcherInterpreter; import com.devonfw.cobigen.api.extension.TriggerInterpreter; import com.devonfw.cobigen.impl.extension.PluginRegistry; +import com.devonfw.cobigen.test.matchers.MatcherToMatcher; +import com.devonfw.cobigen.test.matchers.VariableAssignmentToMatcher; /** A mock factory to simply setup a mocked java plug-in to enable system tests. */ public class PluginMockFactory { + /** * Creates simple to debug test data, which includes only one object as input. A {@link TriggerInterpreter * TriggerInterpreter} will be mocked with all necessary supplier classes to mock a simple java trigger @@ -39,6 +41,7 @@ public String toString() { }; // Pre-processing: Mocking + GeneratorPluginActivator activator = mock(GeneratorPluginActivator.class); TriggerInterpreter triggerInterpreter = mock(TriggerInterpreter.class); MatcherInterpreter matcher = mock(MatcherInterpreter.class); InputReader inputReader = mock(InputReader.class); @@ -68,7 +71,7 @@ public String toString() { new VariableAssignmentToMatcher(equalTo("regex"), equalTo("entityName"), equalTo("3")))))) .thenReturn(variables); - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + PluginRegistry.registerTriggerInterpreter(triggerInterpreter, activator); return input; } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java index 8eb12321c7..2edfd36512 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/CobiGenFactory.java @@ -13,7 +13,6 @@ import com.devonfw.cobigen.impl.config.ConfigurationHolder; import com.devonfw.cobigen.impl.config.ContextConfiguration; import com.devonfw.cobigen.impl.extension.PluginRegistry; -import com.devonfw.cobigen.impl.extension.ServiceLookup; import com.devonfw.cobigen.impl.healthcheck.HealthCheckImpl; import com.devonfw.cobigen.impl.util.FileSystemUtil; @@ -22,10 +21,6 @@ */ public class CobiGenFactory { - static { - ServiceLookup.detectServices(); - } - /** * Creates a new {@link CobiGen} with a given {@link ContextConfiguration}. * diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java new file mode 100644 index 0000000000..7e0d512ae4 --- /dev/null +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java @@ -0,0 +1,86 @@ +package com.devonfw.cobigen.impl.extension; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.ServiceLoader; +import java.util.Set; + +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; +import com.devonfw.cobigen.api.extension.TextTemplateEngine; + +/** + * This class is is the manual implementation of a Lazy {@link ServiceLoader} allowing introspection without + * instantiating a plug-in. Introspection get's supported by JDK 9 earliest by the ServiceLoader + * implementation of JDK. This class can be treated as a workaround for JDK 8 and can be removed as soon as we + * raise the lower limit of JDK support to at least JDK 9. + */ +public class ClassServiceLoader { + + /** Logger instance. */ + private static final Logger LOG = LoggerFactory.getLogger(ClassServiceLoader.class); + + private static Set> generatorPluginActivatorClasses = new HashSet<>(); + + private static Set> templateEngineClasses = new HashSet<>(); + + static { + lookupServices(GeneratorPluginActivator.class, generatorPluginActivatorClasses); + lookupServices(TextTemplateEngine.class, templateEngineClasses); + } + + @SuppressWarnings("unchecked") + private static void lookupServices(Class extensionType, Set> clazzSet) { + try { + Enumeration foundGeneratorPluginActivators = + ClassLoader.getSystemResources("META-INF/services/" + extensionType.getCanonicalName()); + + while (foundGeneratorPluginActivators.hasMoreElements()) { + URL url = foundGeneratorPluginActivators.nextElement(); + String activatorClassName = null; + try { + URLConnection con = url.openConnection(); + try (InputStream in = con.getInputStream()) { + List lines = IOUtils.readLines(in, Charsets.UTF_8); + if (!lines.isEmpty()) { + activatorClassName = lines.get(0); + Class loadClass = + ClassServiceLoader.class.getClassLoader().loadClass(activatorClassName); + if (extensionType.isAssignableFrom(loadClass)) { + clazzSet.add((Class) loadClass); + } else { + LOG.warn("ServiceLoader extension with class {} is not a subclass of {}. Skipping...", + activatorClassName, extensionType.getCanonicalName()); + } + } + } + } catch (IOException e) { + LOG.warn("Could not read plug-in at {}", url, LOG.isDebugEnabled() ? e : null); + } catch (ClassNotFoundException e) { + LOG.warn("Could not load plug-in with class {}", activatorClassName, + LOG.isDebugEnabled() ? e : null); + } + } + } catch (Throwable e1) { + LOG.error("Unable to retrieve {} by ServiceLoader interface", extensionType, e1); + } + } + + public static Set> getGeneratorPluginActivatorClasses() { + return generatorPluginActivatorClasses; + } + + public static Set> getTemplateEngineClasses() { + return templateEngineClasses; + } + +} diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index a55c69d2d8..74fcaedb62 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -1,16 +1,18 @@ package com.devonfw.cobigen.impl.extension; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; +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; @@ -18,7 +20,9 @@ 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; /** @@ -29,23 +33,21 @@ public class PluginRegistry { /** * Currently registered {@link Merger}s mapped by their type */ - private static Map registeredMerger = - Collections.synchronizedMap(Maps. newHashMap()); + private static Map registeredMerger = Maps. newHashMap(); - /** - * Currently registered {@link TriggerInterpreter}s mapped by their type - */ + /** Currently registered {@link TriggerInterpreter}s mapped by their type */ private static Map registeredTriggerInterpreter = - Collections.synchronizedMap(Maps. newHashMap()); + Maps. newHashMap(); - /** - * List of registered plugins - */ - private static List pluginsList = new ArrayList<>(); + /** Currently registered {@link TriggerInterpreter}s mapped by their supporting file extensions */ + private static Multimap registeredTriggerInterpreterByFileExtension = + HashMultimap. create(); - /** - * Assigning logger to PluginRegistry - */ + /** List of registered plugins */ + private static Map, GeneratorPluginActivator> loadedPlugins = + new HashMap<>(); + + /** Assigning logger to PluginRegistry */ private static final Logger LOG = LoggerFactory.getLogger(PluginRegistry.class); /*** @@ -56,28 +58,34 @@ public class PluginRegistry { * plug-in to be loaded * @param * Type of the plug-in interface + * @return the instantiated {@link GeneratorPluginActivator} */ - public static void loadPlugin(Class generatorPlugin) { + 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 - if (((GeneratorPluginActivator) plugin).bindMerger() != null) { - for (Merger merger : ((GeneratorPluginActivator) plugin).bindMerger()) { - PluginRegistry.registerMerger(merger); + GeneratorPluginActivator activator = (GeneratorPluginActivator) plugin; + if (activator.bindMerger() != null) { + for (Merger merger : activator.bindMerger()) { + registerMerger(merger); } // adds merger plugins to notifyable list - pluginsList.add(plugin); } // Collect TriggerInterpreters - if (((GeneratorPluginActivator) plugin).bindTriggerInterpreter() != null) { - for (TriggerInterpreter triggerInterpreter : ((GeneratorPluginActivator) plugin) - .bindTriggerInterpreter()) { - PluginRegistry.registerTriggerInterpreter(triggerInterpreter); + 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( @@ -91,7 +99,7 @@ public static void loadPlugin(Class gene * @param merger * to be registered */ - public static void registerMerger(Merger merger) { + private static void registerMerger(Merger merger) { if (merger == null || StringUtils.isEmpty(merger.getType())) { throw new IllegalArgumentException( @@ -106,14 +114,23 @@ public static void registerMerger(Merger merger) { * * @param triggerInterpreter * to be registered + * @param plugin + * the plugin the trigger interpreter is located in */ - public static void registerTriggerInterpreter(TriggerInterpreter triggerInterpreter) { + 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); + } + } LOG.debug("TriggerInterpreter for type '{}' registered ({}).", triggerInterpreter.getType(), triggerInterpreter.getClass().getCanonicalName()); } @@ -131,7 +148,23 @@ public static Merger getMerger(String mergerType) { if (mergerType == null) { return null; } + Merger merger = registeredMerger.get(mergerType); + if (merger == null) { + for (Class activator : ClassServiceLoader + .getGeneratorPluginActivatorClasses()) { + if (activator.isAnnotationPresent(Activation.class)) { + Activation activation = activator.getAnnotation(Activation.class); + String[] byMergeStrategy = activation.byMergeStrategy(); + Arrays.sort(byMergeStrategy); + if (Arrays.binarySearch(byMergeStrategy, mergerType) >= 0) { + loadPlugin(activator); + break; + } + } + } + merger = registeredMerger.get(mergerType); + } if (merger != null) { merger = ProxyFactory.getProxy(merger); } @@ -150,6 +183,7 @@ public static TriggerInterpreter getTriggerInterpreter(String triggerType) { if (triggerType == null) { return null; } + TriggerInterpreter triggerInterpreter = registeredTriggerInterpreter.get(triggerType); if (triggerInterpreter != null) { triggerInterpreter = ProxyFactory.getProxy(triggerInterpreter); @@ -159,26 +193,49 @@ public static TriggerInterpreter getTriggerInterpreter(String triggerType) { /** * 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 getTriggerInterpreterKeySet() { - return registeredTriggerInterpreter.entrySet().stream().sorted((a, b) -> { - Priority priorityA = getPriority(a.getValue()); - Priority priorityB = getPriority(b.getValue()); - return SignedBytes.compare(priorityA.getRank(), priorityB.getRank()); - }).map(e -> e.getKey()).collect(Collectors.toList()); + public static List getTriggerInterpreters(Path inputPath) { + + String extension = FilenameUtils.getExtension(inputPath.getFileName().toString()); + if (!registeredTriggerInterpreterByFileExtension.containsKey(extension)) { + for (Class activatorClass : ClassServiceLoader + .getGeneratorPluginActivatorClasses()) { + if (activatorClass.isAnnotationPresent(Activation.class)) { + Activation activation = activatorClass.getAnnotation(Activation.class); + String[] byFileExtension = activation.byFileExtension(); + Arrays.sort(byFileExtension); + if (Arrays.binarySearch(byFileExtension, extension) >= 0) { + loadPlugin(activatorClass); + } + } + } + } + + 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; } /** - * @param triggerInterpreter - * {@link TriggerInterpreter} + * @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(TriggerInterpreter triggerInterpreter) { + private static Priority getPriority(Class clazz) { Priority priority; - if (triggerInterpreter.getClass().isAnnotationPresent(ReaderPriority.class)) { - ReaderPriority[] annotation = triggerInterpreter.getClass().getAnnotationsByType(ReaderPriority.class); + if (clazz.getClass().isAnnotationPresent(ReaderPriority.class)) { + ReaderPriority[] annotation = clazz.getClass().getAnnotationsByType(ReaderPriority.class); priority = annotation[0].value(); } else { try { @@ -200,7 +257,7 @@ private static Priority getPriority(TriggerInterpreter triggerInterpreter) { */ public static void notifyPlugins(Path configFolder) { - for (Object plugin : pluginsList) { + for (Object plugin : loadedPlugins.values()) { ((GeneratorPluginActivator) plugin).setProjectRoot(configFolder); } } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ServiceLookup.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ServiceLookup.java deleted file mode 100644 index f03ac4f640..0000000000 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ServiceLookup.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.devonfw.cobigen.impl.extension; - -import java.util.Iterator; -import java.util.ServiceLoader; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.devonfw.cobigen.api.extension.GeneratorPluginActivator; -import com.devonfw.cobigen.api.extension.TextTemplateEngine; - -/** Service lookup implementation. Will be called once on loading CobiGen implementation. */ -public class ServiceLookup { - - /** Logger instance. */ - private static final Logger LOG = LoggerFactory.getLogger(ServiceLookup.class); - - /** - * Detects plug-ins as well as template engines on startup by service loader mechanism. - */ - public static void detectServices() { - - // lookup plug-ins - Iterator pluginIterator = - ServiceLoader.load(GeneratorPluginActivator.class).iterator(); - if (pluginIterator.hasNext()) { - LOG.info("Loading plug-ins"); - } else { - LOG.error("No plug-ins found!"); - } - while (pluginIterator.hasNext()) { - GeneratorPluginActivator loadedPlugin = pluginIterator.next(); - LOG.debug(" * {} found", loadedPlugin.getClass().getName()); - PluginRegistry.loadPlugin(loadedPlugin.getClass()); - } - - // lookup template engines - Iterator tempEngineIterator = ServiceLoader.load(TextTemplateEngine.class).iterator(); - if (tempEngineIterator.hasNext()) { - LOG.info("Loading template engines"); - } else { - LOG.error("No template engines found!"); - } - while (tempEngineIterator.hasNext()) { - TextTemplateEngine loadedPlugin = tempEngineIterator.next(); - LOG.debug(" * {} found", loadedPlugin.getClass().getName()); - TemplateEngineRegistry.register(loadedPlugin.getClass()); - } - } -} diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/TemplateEngineRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/TemplateEngineRegistry.java index e6b3c09a99..fbd06c14a5 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/TemplateEngineRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/TemplateEngineRegistry.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.devonfw.cobigen.api.annotation.Name; import com.devonfw.cobigen.api.exception.CobiGenRuntimeException; import com.devonfw.cobigen.api.extension.TextTemplateEngine; import com.devonfw.cobigen.impl.aop.ProxyFactory; @@ -32,19 +33,21 @@ public class TemplateEngineRegistry { * type of the {@link TextTemplateEngine template engine} to be registered * @param templateEngine * {@link TextTemplateEngine template engine} to be registered + * @param name + * of the template engine */ - public static void register(Class templateEngine) { + public static void register(Class templateEngine, String name) { try { TextTemplateEngine engine = templateEngine.newInstance(); LOG.info("Register template engine '{}'.", templateEngine.getCanonicalName()); - if (StringUtils.isNotBlank(engine.getName())) { - if (registeredEngines.containsKey(engine.getName())) { + if (StringUtils.isNotBlank(name)) { + if (registeredEngines.containsKey(name)) { throw new CobiGenRuntimeException( - "An template engine with type " + engine.getName() + " has already been registered."); + "An template engine with name " + name + " has already been registered."); } - registeredEngines.put(engine.getName(), engine); + registeredEngines.put(name, engine); } else { throw new CobiGenRuntimeException("Cannot register a template engine without a type."); } @@ -65,6 +68,23 @@ public static void register(Class templateEngi public static TextTemplateEngine getEngine(String name) { TextTemplateEngine templateEngine = registeredEngines.get(name); + if (templateEngine == null) { + for (Class engine : ClassServiceLoader.getTemplateEngineClasses()) { + if (engine.isAnnotationPresent(Name.class)) { + Name engineNameAnnotation = engine.getAnnotation(Name.class); + String engineName = engineNameAnnotation.value(); + if (name.equals(engineName)) { + register(engine, engineName); + break; + } + } else { + LOG.warn("Template engine '{}' should have a name specified by @Name annotation.", + engine.getClass().getCanonicalName()); + } + } + } + + templateEngine = registeredEngines.get(name); if (templateEngine == null) { throw new CobiGenRuntimeException("No template engine with name '" + name + "' registered."); } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java index 789e418fe8..3c2bb5e8e6 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java @@ -164,11 +164,6 @@ public Object read(Path path, Charset inputCharset, Object... additionalArgument return inputInterpreter.read(path, inputCharset, additionalArguments); } - @Override - public Boolean isMostLikelyReadable(String type, Path path) { - return inputInterpreter.isMostLikelyReadable(type, path); - } - @Override public List getMatchingIncrements(Object matcherInput) throws InvalidConfigurationException { return configurationInterpreter.getMatchingIncrements(matcherInput); diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java index 9122b7f57e..c48e01ff29 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java @@ -67,23 +67,14 @@ public Object read(String type, Path path, Charset inputCharset, Object... addit @Override public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException { - List triggerInterpreterKeySet = PluginRegistry.getTriggerInterpreterKeySet(); + List triggerInterpreters = PluginRegistry.getTriggerInterpreters(path); // We first try to find an input reader that is most likely readable - Map readableCache = new HashMap<>(); + Map readableCache = new HashMap<>(); Object readable = null; - // First find plug-ins which might come from outside of cobigen space (externally developed plugins) - for (String triggerType : triggerInterpreterKeySet) { - readable = readInput(path, inputCharset, readableCache, triggerType, true, additionalArguments); - if (readable != null) { - return readable; - } - } - - // If no external input reader was found, check internal input readers - for (String triggerType : readableCache.keySet()) { - readable = readInput(path, inputCharset, readableCache, triggerType, null, additionalArguments); + for (TriggerInterpreter triggerInterpreter : triggerInterpreters) { + readable = readInput(path, inputCharset, readableCache, triggerInterpreter, additionalArguments); if (readable != null) { return readable; } @@ -102,33 +93,27 @@ public Object read(Path path, Charset inputCharset, Object... additionalArgument * of the input to be used * @param readableCache * HashMap of TriggerInterpreter and Boolean - * @param triggerType + * @param triggerInterpreter * type of TriggerInterpreter - * @param expectedResult - * Boolean expected result of isMostLikelyReadable check * @param additionalArguments * depending on the InputReader implementation * @return Object that is a valid input or null if the file cannot be read by any InputReader */ - private Object readInput(Path path, Charset inputCharset, Map readableCache, String triggerType, - Boolean expectedResult, Object... additionalArguments) { - String readerType = "EXTERNAL"; - if (expectedResult == null) { - readerType = "INTERNAL"; - } + private Object readInput(Path path, Charset inputCharset, Map readableCache, + TriggerInterpreter triggerInterpreter, Object... additionalArguments) { try { - if (isMostLikelyReadable(triggerType, path, readableCache) == expectedResult) { - LOG.info("Try reading input {} with {} inputreader '{}'...", path, readerType, triggerType); - return getInputReader(triggerType).read(path, inputCharset, additionalArguments); + if (isMostLikelyReadable(triggerInterpreter, path, readableCache)) { + LOG.info("Try reading input {} with inputreader '{}'...", path, triggerInterpreter); + return triggerInterpreter.getInputReader().read(path, inputCharset, additionalArguments); } } catch (InputReaderException e) { LOG.debug( - "Was not able to read input {} with {} inputreader '{}' although it was reported to be most likely readable. Trying next input reader...", - path, readerType, triggerType, e); + "Was not able to read input {} with inputreader '{}' although it was reported to be most likely readable. Trying next input reader...", + path, triggerInterpreter, e); } catch (Throwable e) { LOG.debug( - "While reading the input {} with the {} inputreader {}, an exception occured. Trying next input reader...", - path, readerType, triggerType, e); + "While reading the input {} with the inputreader {}, an exception occured. Trying next input reader...", + path, triggerInterpreter, e); } return null; } @@ -136,24 +121,20 @@ private Object readInput(Path path, Charset inputCharset, Map r /** * Checks if the input is most likely readable and fills the provided cache * - * @param type - * String of TriggerType + * @param triggerInterpreter + * {@link TriggerInterpreter} to be used * @param path * the file Path * @param cache * Map of TriggerType and isMostLikelyReadable check results * @return Boolean true if readable by external plugin, null for internal plugin and false if not readable */ - private Boolean isMostLikelyReadable(String type, Path path, Map cache) { - if (!cache.containsKey(type)) { - cache.put(type, isMostLikelyReadable(type, path)); + private Boolean isMostLikelyReadable(TriggerInterpreter triggerInterpreter, Path path, + Map cache) { + if (!cache.containsKey(triggerInterpreter)) { + cache.put(triggerInterpreter, triggerInterpreter.getInputReader().isMostLikelyReadable(path)); } - return cache.get(type); - } - - @Override - public Boolean isMostLikelyReadable(String type, Path path) { - return getInputReader(type).isMostLikelyReadable(path); + return cache.get(triggerInterpreter); } /** diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/stubs/TemplateEngineStub.java b/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/stubs/TemplateEngineStub.java index 03ec6da6ba..6cbe1ecd74 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/stubs/TemplateEngineStub.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/stubs/TemplateEngineStub.java @@ -4,19 +4,16 @@ import java.nio.file.Path; import java.util.Map; +import com.devonfw.cobigen.api.annotation.Name; import com.devonfw.cobigen.api.extension.TextTemplate; import com.devonfw.cobigen.api.extension.TextTemplateEngine; /** * Fake template engine matching the default template engine FreeMarker. */ +@Name("FreeMarker") public class TemplateEngineStub implements TextTemplateEngine { - @Override - public String getName() { - return "FreeMarker"; - } - @Override public String getTemplateFileEnding() { return ".ftl"; diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/unittest/config/common/AbstractUnitTest.java b/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/unittest/config/common/AbstractUnitTest.java index ed340a5738..8bf458d65d 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/unittest/config/common/AbstractUnitTest.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/test/java/com/devonfw/cobigen/unittest/config/common/AbstractUnitTest.java @@ -9,6 +9,6 @@ public abstract class AbstractUnitTest { static { - TemplateEngineRegistry.register(TemplateEngineStub.class); + TemplateEngineRegistry.register(TemplateEngineStub.class, "FreeMarker"); } } From fcc929324a2e793afbca4a5455e1d315006c0b68 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Wed, 9 Sep 2020 21:42:58 +0200 Subject: [PATCH 06/14] #1209 cleanup --- .../generator/GenerationProcessorImpl.java | 52 +------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/GenerationProcessorImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/GenerationProcessorImpl.java index f670e17c93..3557aa420d 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/GenerationProcessorImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/GenerationProcessorImpl.java @@ -105,6 +105,8 @@ public GenerationProcessorImpl(ConfigurationHolder configurationHolder, InputRes /** * Loads the logic classes passed to assure a singleton instance for the complete generation. Mapping from * simple type to instance. + * @param progressCallback + * callback for tracking the progress * @param logicClasses * logic classes to instantiate. */ @@ -487,56 +489,6 @@ private Map buildModel(TriggerInterpreter triggerInterpreter, Tr return model; } - // /** - // * Collects all input objects. Especially, resolves container inputs. - // * @param input - // * object - // * @param triggerInterpreter - // * {@link TriggerInterpreter} to be used - // * @param trigger - // * {@link Trigger} to be used - // * @return the {@link List} of collected input objects. - // */ - // private List collectInputObjects(Object input, TriggerInterpreter triggerInterpreter, Trigger - // trigger) { - // - // InputReader inputReader = triggerInterpreter.getInputReader(); - // List inputObjects = new ArrayList<>(); - // if (inputInterpreter.combinesMultipleInputs(input)) { - // - // // check whether the inputs should be retrieved recursively - // boolean retrieveInputsRecursively = false; - // for (ContainerMatcher containerMatcher : trigger.getContainerMatchers()) { - // MatcherTo matcherTo = new MatcherTo(containerMatcher.getType(), containerMatcher.getValue(), input); - // if (triggerInterpreter.getMatcher().matches(matcherTo)) { - // if (!retrieveInputsRecursively) { - // retrieveInputsRecursively = containerMatcher.isRetrieveObjectsRecursively(); - // } else { - // break; - // } - // } - // } - // - // if (retrieveInputsRecursively) { - // inputObjects = inputReader.getInputObjectsRecursively(input, trigger.getInputCharset()); - // } else { - // inputObjects = inputReader.getInputObjects(input, trigger.getInputCharset()); - // } - // - // // Remove non matching inputs - // Iterator it = inputObjects.iterator(); - // while (it.hasNext()) { - // Object next = it.next(); - // if (!matcherEvaluator.matches(next, trigger.getMatcher(), triggerInterpreter)) { - // it.remove(); - // } - // } - // } else { - // inputObjects.add(input); - // } - // return inputObjects; - // } - /** * Generates the given template contents using the given model and writes the contents into the given * {@link File} From 81ab06646c656de835234a54300dc21c6726a666 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Wed, 9 Sep 2020 22:23:58 +0200 Subject: [PATCH 07/14] #1228 added possibility for activation on folder selection --- .../cobigen/api/annotation/Activation.java | 5 ++++ .../impl/extension/PluginRegistry.java | 23 ++++++++++++++++--- .../impl/validator/InputValidator.java | 5 ++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java index 16845ede53..184ad3bf84 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/annotation/Activation.java @@ -21,6 +21,11 @@ */ String[] byFileExtension() default {}; + /** + * @return whether this plug-in can read a folder as input. + */ + boolean byFolder() default false; + /** * @return the merge strategies provided by this plug-in, which will cause the plug-in lazily to be loaded * just in case a merge strategy is requested which is provided by this plug-in diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index 74fcaedb62..f0d27cebe0 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -43,6 +43,9 @@ public class PluginRegistry { 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<>(); @@ -201,15 +204,28 @@ public static TriggerInterpreter getTriggerInterpreter(String triggerType) { */ public static List getTriggerInterpreters(Path inputPath) { - String extension = FilenameUtils.getExtension(inputPath.getFileName().toString()); - if (!registeredTriggerInterpreterByFileExtension.containsKey(extension)) { + String extension; + if (inputPath.toFile().isFile()) { + extension = FilenameUtils.getExtension(inputPath.getFileName().toString()); for (Class activatorClass : ClassServiceLoader .getGeneratorPluginActivatorClasses()) { if (activatorClass.isAnnotationPresent(Activation.class)) { Activation activation = activatorClass.getAnnotation(Activation.class); String[] byFileExtension = activation.byFileExtension(); Arrays.sort(byFileExtension); - if (Arrays.binarySearch(byFileExtension, extension) >= 0) { + if (Arrays.binarySearch(byFileExtension, extension) >= 0 + && !loadedPlugins.containsKey(activatorClass)) { + loadPlugin(activatorClass); + } + } + } + } else { // directory + extension = FOLDER; + for (Class activatorClass : ClassServiceLoader + .getGeneratorPluginActivatorClasses()) { + if (activatorClass.isAnnotationPresent(Activation.class)) { + Activation activation = activatorClass.getAnnotation(Activation.class); + if (activation.byFolder() && !loadedPlugins.containsKey(activatorClass)) { loadPlugin(activatorClass); } } @@ -227,6 +243,7 @@ public static List getTriggerInterpreters(Path inputPath) { } /** + * Extracts the {@link ReaderPriority} of a trigger interpreter * @param clazz * class to get the {@link ReaderPriority} annotation from (commonly the TriggerInterpreter * classes) diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/validator/InputValidator.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/validator/InputValidator.java index 9ee76c26b5..a97e038261 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/validator/InputValidator.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/validator/InputValidator.java @@ -59,9 +59,8 @@ public static void validateTriggerInterpreter(TriggerInterpreter triggerInterpre */ public static void validateTriggerInterpreter(TriggerInterpreter triggerInterpreter, String triggerType) { if (triggerInterpreter == null) { - throw new InvalidConfigurationException( - "No TriggerInterpreter " + (triggerType != null ? "for type '" + triggerType + "' " : "") - + " provided! You may miss a plug-in."); + throw new InvalidConfigurationException("No TriggerInterpreter " + + (triggerType != null ? "for type '" + triggerType + "' " : "") + "provided! You may miss a plug-in."); } if (triggerInterpreter.getInputReader() == null) { From 5d9f9c72142d35422ac3addd3bec93017216bf73 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Thu, 10 Sep 2020 01:13:03 +0200 Subject: [PATCH 08/14] set new version to make compatibility recognition easier --- .../devonfw/cobigen/api/InputInterpreter.java | 19 ------------ .../cobigen/impl/generator/CobiGenImpl.java | 6 ---- .../impl/generator/InputInterpreterImpl.java | 29 ------------------- cobigen/cobigen-core-parent/pom.xml | 2 +- 4 files changed, 1 insertion(+), 55 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java index 7c2183f830..2509396b58 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java +++ b/cobigen/cobigen-core-parent/cobigen-core-api/src/main/java/com/devonfw/cobigen/api/InputInterpreter.java @@ -28,25 +28,6 @@ public interface InputInterpreter { */ public List resolveContainers(Object input); - /** - * Reads the content at a path via a fitting {@link InputReader} and returns a CobiGen compliant input. - * @param type - * of the input to be read. Is used to resolve a fitting InputReader - * @param path - * the {@link Path} to the object. Can also point to a folder - * @param inputCharset - * of the input to be used - * @param additionalArguments - * depending on the InputReader implementation - * @return Object that is a valid input - * @throws InputReaderException - * if the Path cannot be read - * @throws IllegalArgumentException - * if the provided additional arguments do not suffice the used InputReader - */ - public Object read(String type, Path path, Charset inputCharset, Object... additionalArguments) - throws InputReaderException; - /** * Reads the content at a path via the first fitting {@link InputReader} and returns a CobiGen compliant * input. Supports at least one EXTERNAL input reader with the same isMostLikelyReadable criteria than an diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java index 3c2bb5e8e6..6385b876a9 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/CobiGenImpl.java @@ -153,12 +153,6 @@ public List resolveContainers(Object input) { return inputInterpreter.resolveContainers(input); } - @Override - public Object read(String type, Path path, Charset inputCharset, Object... additionalArguments) - throws InputReaderException { - return inputInterpreter.read(type, path, inputCharset, additionalArguments); - } - @Override public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException { return inputInterpreter.read(path, inputCharset, additionalArguments); diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java index c48e01ff29..10e15727a9 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java @@ -14,10 +14,7 @@ import com.devonfw.cobigen.api.InputInterpreter; import com.devonfw.cobigen.api.annotation.Cached; -import com.devonfw.cobigen.api.exception.CobiGenRuntimeException; import com.devonfw.cobigen.api.exception.InputReaderException; -import com.devonfw.cobigen.api.exception.PluginNotAvailableException; -import com.devonfw.cobigen.api.extension.InputReader; import com.devonfw.cobigen.api.extension.TriggerInterpreter; import com.devonfw.cobigen.impl.config.entity.Trigger; import com.devonfw.cobigen.impl.extension.PluginRegistry; @@ -58,13 +55,6 @@ public List resolveContainers(Object input) { return inputs; } - // not cached by intention - @Override - public Object read(String type, Path path, Charset inputCharset, Object... additionalArguments) - throws InputReaderException { - return getInputReader(type).read(path, inputCharset, additionalArguments); - } - @Override public Object read(Path path, Charset inputCharset, Object... additionalArguments) throws InputReaderException { List triggerInterpreters = PluginRegistry.getTriggerInterpreters(path); @@ -137,23 +127,4 @@ private Boolean isMostLikelyReadable(TriggerInterpreter triggerInterpreter, Path return cache.get(triggerInterpreter); } - /** - * @param type - * of the input - * @return InputReader for the given type. - * @throws CobiGenRuntimeException - * if no InputReadercould be found - */ - private InputReader getInputReader(String type) { - TriggerInterpreter triggerInterpreter = PluginRegistry.getTriggerInterpreter(type); - if (triggerInterpreter == null) { - throw new PluginNotAvailableException("TriggerInterpreter", type); - } - if (triggerInterpreter.getInputReader() == null) { - throw new PluginNotAvailableException("InputReader", type); - } - - return triggerInterpreter.getInputReader(); - } - } diff --git a/cobigen/cobigen-core-parent/pom.xml b/cobigen/cobigen-core-parent/pom.xml index cf5dc18cb6..2c6929d1ef 100644 --- a/cobigen/cobigen-core-parent/pom.xml +++ b/cobigen/cobigen-core-parent/pom.xml @@ -12,7 +12,7 @@ - 6.2.0-SNAPSHOT + 7.0.0-SNAPSHOT From c41932caa0fa9ae86eae581a6f97ad4e35f934c4 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Fri, 11 Sep 2020 11:12:27 +0200 Subject: [PATCH 09/14] #1228 fixing service detection and introduced more logging --- .../cobigen-core-systemtest/pom.xml | 4 +-- .../impl/extension/ClassServiceLoader.java | 29 +++++++++++++++++-- .../impl/extension/PluginRegistry.java | 12 ++++++++ .../TriggerMatchingEvaluatorImpl.java | 6 ++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml b/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml index 25d8f9eaa9..15b705e0b4 100644 --- a/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml +++ b/cobigen/cobigen-core-parent/cobigen-core-systemtest/pom.xml @@ -26,13 +26,13 @@ ${project.groupId} tempeng-freemarker - 2.2.0-SNAPSHOT + 7.0.0-SNAPSHOT test ${project.groupId} javaplugin - 2.0.0 + 7.0.0-SNAPSHOT test diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java index 7e0d512ae4..caa4b6042e 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java @@ -29,33 +29,49 @@ public class ClassServiceLoader { /** Logger instance. */ private static final Logger LOG = LoggerFactory.getLogger(ClassServiceLoader.class); + /** Classes detected as GeneratorPluginActivators */ private static Set> generatorPluginActivatorClasses = new HashSet<>(); + /** Classes detected as TemplateEngines */ private static Set> templateEngineClasses = new HashSet<>(); static { + LOG.info("Loading plug-in activators..."); lookupServices(GeneratorPluginActivator.class, generatorPluginActivatorClasses); + LOG.info("Loading template engines..."); lookupServices(TextTemplateEngine.class, templateEngineClasses); } + /** + * Detects services of extensions type and adds them to the clazzSet + * @param + * the extension type to be found by {@link ServiceLoader} mechanism + * @param extensionType + * the extension type + * @param clazzSet + * the set to add the detected classes + */ @SuppressWarnings("unchecked") private static void lookupServices(Class extensionType, Set> clazzSet) { try { - Enumeration foundGeneratorPluginActivators = - ClassLoader.getSystemResources("META-INF/services/" + extensionType.getCanonicalName()); + Enumeration foundGeneratorPluginActivators = Thread.currentThread().getContextClassLoader() + .getResources("META-INF/services/" + extensionType.getName()); while (foundGeneratorPluginActivators.hasMoreElements()) { URL url = foundGeneratorPluginActivators.nextElement(); + LOG.debug("Found classpath entry: {}", url); String activatorClassName = null; try { URLConnection con = url.openConnection(); try (InputStream in = con.getInputStream()) { List lines = IOUtils.readLines(in, Charsets.UTF_8); + LOG.debug("Lines of service loader file: {}", lines); if (!lines.isEmpty()) { activatorClassName = lines.get(0); Class loadClass = ClassServiceLoader.class.getClassLoader().loadClass(activatorClassName); if (extensionType.isAssignableFrom(loadClass)) { + LOG.info("Found {} {}", extensionType.getSimpleName(), activatorClassName); clazzSet.add((Class) loadClass); } else { LOG.warn("ServiceLoader extension with class {} is not a subclass of {}. Skipping...", @@ -70,15 +86,24 @@ private static void lookupServices(Class extensionType, Set> getGeneratorPluginActivatorClasses() { return generatorPluginActivatorClasses; } + /** + * @return the detected classes of {@link TextTemplateEngine} + */ public static Set> getTemplateEngineClasses() { return templateEngineClasses; } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index f0d27cebe0..82a2d2a41c 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -207,27 +207,39 @@ 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("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."); } } } diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/TriggerMatchingEvaluatorImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/TriggerMatchingEvaluatorImpl.java index 4479eccad8..945622d268 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/TriggerMatchingEvaluatorImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/TriggerMatchingEvaluatorImpl.java @@ -46,6 +46,12 @@ public List getMatchingTriggers(Object matcherInput) { List matchingTrigger = Lists.newLinkedList(); for (Trigger trigger : configurationHolder.readContextConfiguration().getTriggers()) { TriggerInterpreter triggerInterpreter = PluginRegistry.getTriggerInterpreter(trigger.getType()); + if (triggerInterpreter == null) { + continue; + // trigger interpreter not yet activated as the plug-in was not yet used. + // unfortunately the invariant here is, that the CobiGen user has once called CobigenImpl#read + // to get the matcher input + } InputValidator.validateTriggerInterpreter(triggerInterpreter, trigger); LOG.debug("Check {} to match the input.", trigger); From 2d07a0b6552db6b4bda4f763ad10ccc9daf41555 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Fri, 11 Sep 2020 16:38:14 +0200 Subject: [PATCH 10/14] #1228 fixing small bug for folder inputs --- .../com/devonfw/cobigen/impl/extension/PluginRegistry.java | 3 +++ .../cobigen/impl/generator/InputInterpreterImpl.java | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index 82a2d2a41c..0dbeccf1db 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -133,6 +133,9 @@ public static void registerTriggerInterpreter(TriggerInterpreter triggerInterpre 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()); diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java index 10e15727a9..4ed6e59e4e 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/generator/InputInterpreterImpl.java @@ -95,6 +95,10 @@ private Object readInput(Path path, Charset inputCharset, Map cache) { From 88ab068cbccf95264c02bb91b3159cd4bcb29294 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Fri, 11 Sep 2020 17:21:12 +0200 Subject: [PATCH 11/14] #1228 further logging --- .../impl/extension/PluginRegistry.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java index 0dbeccf1db..e1dbaa54f5 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/PluginRegistry.java @@ -31,7 +31,7 @@ public class PluginRegistry { /** - * Currently registered {@link Merger}s mapped by their type + * Currently registered {@link Merger}s mapped by their merge strategy */ private static Map registeredMerger = Maps. newHashMap(); @@ -142,34 +142,44 @@ public static void registerTriggerInterpreter(TriggerInterpreter triggerInterpre } /** - * Returns the {@link Merger} for the given mergerType + * Returns the {@link Merger} for the given merge strategy * - * @param mergerType + * @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 mergerType) { + public static Merger getMerger(String mergeStrategy) { - if (mergerType == null) { + if (mergeStrategy == null) { return null; } - Merger merger = registeredMerger.get(mergerType); + Merger merger = registeredMerger.get(mergeStrategy); if (merger == null) { - for (Class activator : ClassServiceLoader + LOG.debug("Trying to find merger for type '{}'", mergeStrategy); + for (Class activatorClass : ClassServiceLoader .getGeneratorPluginActivatorClasses()) { - if (activator.isAnnotationPresent(Activation.class)) { - Activation activation = activator.getAnnotation(Activation.class); + 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, mergerType) >= 0) { - loadPlugin(activator); + 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(mergerType); + merger = registeredMerger.get(mergeStrategy); } if (merger != null) { merger = ProxyFactory.getProxy(merger); @@ -225,6 +235,8 @@ public static List getTriggerInterpreters(Path inputPath) { 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."); From af2a97d467528740c190f8b6851b45ba8a7fc33a Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Sun, 13 Sep 2020 21:10:14 +0200 Subject: [PATCH 12/14] #1228 classloading + further logging --- .../cobigen/impl/extension/ClassServiceLoader.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java index caa4b6042e..89cceeb571 100644 --- a/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java +++ b/cobigen/cobigen-core-parent/cobigen-core/src/main/java/com/devonfw/cobigen/impl/extension/ClassServiceLoader.java @@ -54,8 +54,9 @@ public class ClassServiceLoader { @SuppressWarnings("unchecked") private static void lookupServices(Class extensionType, Set> clazzSet) { try { - Enumeration foundGeneratorPluginActivators = Thread.currentThread().getContextClassLoader() - .getResources("META-INF/services/" + extensionType.getName()); + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + Enumeration foundGeneratorPluginActivators = + contextClassLoader.getResources("META-INF/services/" + extensionType.getName()); while (foundGeneratorPluginActivators.hasMoreElements()) { URL url = foundGeneratorPluginActivators.nextElement(); @@ -68,8 +69,7 @@ private static void lookupServices(Class extensionType, Set loadClass = - ClassServiceLoader.class.getClassLoader().loadClass(activatorClassName); + Class loadClass = contextClassLoader.loadClass(activatorClassName); if (extensionType.isAssignableFrom(loadClass)) { LOG.info("Found {} {}", extensionType.getSimpleName(), activatorClassName); clazzSet.add((Class) loadClass); From 3fd0f35c7ec21a7cbe82a64e716128b146d49d41 Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Sun, 13 Sep 2020 22:20:51 +0200 Subject: [PATCH 13/14] #1213 update wiki docs --- documentation/master-cobigen.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/master-cobigen.asciidoc b/documentation/master-cobigen.asciidoc index 8f4ef21ec7..db08a0a4ac 100644 --- a/documentation/master-cobigen.asciidoc +++ b/documentation/master-cobigen.asciidoc @@ -17,7 +17,7 @@ DISCLAIMER: All Cobigen plugins are compatible with the latest release of Devonf --- -* CobiGen v6.1.2 +* CobiGen v7.0.0 * CobiGen - Java Plug-in v2.2.3 * CobiGen - XML Plug-in v4.2.0 * CobiGen - TypeScript Plug-in v2.4.4 From 31caa74821f726bf6584b4d7f9408f985ab94b1b Mon Sep 17 00:00:00 2001 From: Malte Brunnlieb Date: Sun, 13 Sep 2020 22:20:57 +0200 Subject: [PATCH 14/14] #1213 Set release version --- cobigen/cobigen-core-parent/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cobigen/cobigen-core-parent/pom.xml b/cobigen/cobigen-core-parent/pom.xml index 2c6929d1ef..3f74d065de 100644 --- a/cobigen/cobigen-core-parent/pom.xml +++ b/cobigen/cobigen-core-parent/pom.xml @@ -12,7 +12,7 @@ - 7.0.0-SNAPSHOT + 7.0.0 @@ -22,4 +22,4 @@ cobigen-core-systemtest - + \ No newline at end of file