Skip to content

Commit

Permalink
Merge branch 'master' into release/4.7
Browse files Browse the repository at this point in the history
  • Loading branch information
qqilihq committed Aug 6, 2024
2 parents 547b39e + 9590234 commit e831e8c
Show file tree
Hide file tree
Showing 18 changed files with 233 additions and 82 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ target/
.classpath
.project
.settings/
lib/
nodeDocumentation.json
portDocumentation.json
splashIcons.json
.tycho-consumer-pom.xml
10 changes: 6 additions & 4 deletions application/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: JSON node documentation generator
Bundle-SymbolicName: de.philippkatz.knime.jsondocgen.application;singleton:=true
Bundle-Version: 1.13.5.qualifier
Bundle-Version: 1.14.0.qualifier
Bundle-Vendor: Philipp Katz; Selenium Nodes
Bundle-RequiredExecutionEnvironment: JavaSE-17
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.6.0,5.0.0)",
Expand All @@ -12,7 +12,9 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.6.0,5.0.0)",
org.eclipse.equinox.p2.core;bundle-version="[2.4.100,3.0.0)",
org.eclipse.equinox.p2.engine;bundle-version="[2.4.100,3.0.0)",
org.knime.product;bundle-version="[4.7.0,6.0.0)",
org.apache.log4j;bundle-version="[1.2.0,2.0.0)"
Bundle-ClassPath: .,
lib/gson.jar
org.apache.log4j;bundle-version="[1.2.0,2.0.0)",
com.google.gson;bundle-version="[2.8.6,3.0.0)",
org.knime.workflow.migration;bundle-version="[4.7.0,6.0.0)",
org.mockito.mockito-core;bundle-version="[2.28.2,3.0.0)"
Bundle-ClassPath: .
Bundle-ActivationPolicy: lazy
1 change: 0 additions & 1 deletion application/build.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ source.. = src/,\
resources/
bin.includes = META-INF/,\
.,\
lib/gson.jar,\
plugin.xml,\
resources/
49 changes: 2 additions & 47 deletions application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,13 @@

<modelVersion>4.0.0</modelVersion>
<artifactId>de.philippkatz.knime.jsondocgen.application</artifactId>
<version>1.13.5-SNAPSHOT</version>
<version>1.14.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>

<parent>
<groupId>de.philippkatz.knime.jsondocgen</groupId>
<artifactId>de.philippkatz.knime.jsondocgen</artifactId>
<version>1.13.5-SNAPSHOT</version>
<version>1.14.0-SNAPSHOT</version>
</parent>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>copy-libraries</id>
<phase>initialize</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>lib</outputDirectory>
<stripVersion>true</stripVersion>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<filesets>
<fileset>
<directory>lib</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
* @author Martin Horn, University of Konstanz
* @author Philipp Katz, seleniumnodes.com
*/
@SuppressWarnings("deprecation")
public class JsonNodeDocuGenerator implements IApplication {

private static final Logger LOGGER = Logger.getLogger(JsonNodeDocuGenerator.class);
Expand All @@ -134,6 +135,8 @@ public class JsonNodeDocuGenerator implements IApplication {

private static final String SKIP_SPLASH_ICONS = "-skipSplashIcons";

private static final String SKIP_MIGRATION_RULES = "-skipMigrationRules";

/** Return code in case an error occurs during execution. */
private static final Integer EXIT_EXECUTION_ERROR = Integer.valueOf(1);

Expand Down Expand Up @@ -169,6 +172,8 @@ private static void printUsage() {

private boolean m_skipSplashIcons = false;

private boolean m_skipMigrationRules = false;

private CategoryDocBuilder rootCategoryDoc;

@Override
Expand All @@ -182,8 +187,7 @@ public Object start(final IApplicationContext context) throws Exception {
}
Object o = context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
Display.getDefault();
if (o != null && o instanceof String[]) {
String[] args = (String[]) o;
if (o != null && o instanceof String[] args) {
for (int i = 0; i < args.length; i++) {
if (args[i].equals(DESTINATION_ARG)) {
m_directory = new File(args[i + 1]);
Expand All @@ -199,6 +203,8 @@ public Object start(final IApplicationContext context) throws Exception {
m_skipPortDocumentation = true;
} else if (args[i].equals(SKIP_SPLASH_ICONS)) {
m_skipSplashIcons = true;
} else if (args[i].equals(SKIP_MIGRATION_RULES)) {
m_skipMigrationRules = true;
} else if (args[i].equals("-help")) {
printUsage();
return EXIT_OK;
Expand Down Expand Up @@ -315,6 +321,14 @@ private void generate() throws Exception {
IOUtils.write(Utils.toJson(splashIcons), new FileOutputStream(splashIconsResultFile),
StandardCharsets.UTF_8);
}

if (!m_skipMigrationRules) {
var migrationRuleDocs = MigrationRuleExtractor.extractMigrationRules();
var migrationsResultFile = new File(m_directory, "migrations.json");
LOGGER.info("Writing migrations to " + migrationsResultFile);
IOUtils.write(Utils.toJson(migrationRuleDocs), new FileOutputStream(migrationsResultFile),
StandardCharsets.UTF_8);
}
}

/**
Expand Down Expand Up @@ -412,7 +426,7 @@ private static void processPorts(Collection<Class<? extends PortObject>> portObj
private boolean generate(final File directory, final IRepositoryObject current, final IRepositoryObject parent,
CategoryDocBuilder parentCategory) throws TransformerException, Exception {

if (current instanceof NodeTemplate) {
if (current instanceof NodeTemplate nodeTemplate) {

// skip if not in a sub-category of the category specified
// as argument
Expand All @@ -423,7 +437,6 @@ private boolean generate(final File directory, final IRepositoryObject current,
}
}

NodeTemplate nodeTemplate = (NodeTemplate) current;
NodeFactory<? extends NodeModel> factory = nodeTemplate.createFactoryInstance();

// skip node if not part of the specified plugin
Expand Down Expand Up @@ -480,8 +493,7 @@ private boolean generate(final File directory, final IRepositoryObject current,

CategoryDocBuilder newCategory = parentCategory;

if (current instanceof Category) {
Category category = (Category) current;
if (current instanceof Category category) {
CategoryDocBuilder builder = new CategoryDocBuilder();
builder.setId(category.getID());
builder.setName(category.getName());
Expand Down Expand Up @@ -523,8 +535,7 @@ private boolean generate(final File directory, final IRepositoryObject current,
* @throws Exception
*/
/* package */ static NodeModel createNodeModel(NodeFactory<? extends NodeModel> factory) throws Exception {
if (factory instanceof ConfigurableNodeFactory) {
ConfigurableNodeFactory<?> configurableFactory = (ConfigurableNodeFactory<?>) factory;
if (factory instanceof ConfigurableNodeFactory configurableFactory) {
ModifiableNodeCreationConfiguration config = configurableFactory.createNodeCreationConfig();
// org.knime.core.node.NodeFactory.createNodeModel(NodeCreationConfiguration)
Method method = NodeFactory.class.getDeclaredMethod("createNodeModel", NodeCreationConfiguration.class);
Expand Down Expand Up @@ -571,8 +582,7 @@ private static List<Port> mergePortInfo(List<Port> ports, PortType[] portTypes,
}

/**
* Get runtime port type information via reflection (this information cannot be
* accessed via public API).
* Get runtime port type information.
*
* @param nodeFactory
* The node factory.
Expand Down Expand Up @@ -608,10 +618,9 @@ private static List<Port> mergePortInfo(List<Port> ports, PortType[] portTypes,
List<DynamicPortGroup> dynamicPortGroups = new ArrayList<>();
for (String portGroupName : portsConfig.getPortGroupNames()) {
PortGroupConfiguration groupConfig = portsConfig.getGroup(portGroupName);
if (groupConfig instanceof ConfigurablePortGroup
if (groupConfig instanceof ConfigurablePortGroup configurablePortGroup
&& (groupConfig.definesInputPorts() && portDirection == PortDirection.In
|| groupConfig.definesOutputPorts() && portDirection == PortDirection.Out)) {
ConfigurablePortGroup configurablePortGroup = (ConfigurablePortGroup) groupConfig;
PortType[] supportedTypes = configurablePortGroup.getSupportedPortTypes();
dynamicPortGroups.add(
new DynamicPortGroup(null, null, portGroupName, null, Arrays.stream(supportedTypes)
Expand Down Expand Up @@ -672,7 +681,7 @@ private static boolean isStreamable(NodeModel nodeModel) {
return false;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked" })
private static Optional<String> getBundleName(NodeFactory<?> nodeFactory) {
if (!(nodeFactory instanceof DynamicNodeFactory)) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package de.philippkatz.knime.jsondocgen;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.knime.core.node.NodeFactory;
import org.knime.core.node.extension.NodeFactoryExtensionManager;
import org.knime.workflow.migration.MigrationNodeMatchResult;
import org.knime.workflow.migration.NodeMigrationRuleRegistry;
import org.knime.workflow.migration.model.MigrationNode;
import org.mockito.Mockito;

import de.philippkatz.knime.jsondocgen.docs.MigrationRuleDoc;
import de.philippkatz.knime.jsondocgen.docs.MigrationRuleDoc.MigrationRuleDocBuilder;

/**
* Extract migration rules - their migration “framework” is the probably most
* complex thinkable solution from a technical and API level, yet frighteningly
* limited at the end.
*
* This code tries to extract an original node factory ID and its corresponding
* replacement factory.
*
* Context-specific properties, where the migration depends e.g. on settings are
* _not_ covered - however only few rules actually make use of them.
*
* @author Philipp Katz
*/
public class MigrationRuleExtractor {

private static final Logger LOGGER = Logger.getLogger(MigrationRuleExtractor.class);

public static List<MigrationRuleDoc> extractMigrationRules() {

var migrationRules = NodeMigrationRuleRegistry.getInstance().getRules();
// they replaced this in 5.2 but of course the “get all” is not accessible -
// facepalm; in case it gets removed, we'll need to build this ourselves or get
// it from the “generate node documenation” phase instead
var nodeFactoryExtensions = NodeFactoryExtensionManager.getInstance().getNodeFactoryExtensions();

LOGGER.info(String.format("Generating %s migration rules", migrationRules.size()));

var migrationRuleInfos = new ArrayList<MigrationRuleDoc>();
for (var rule : migrationRules) {
try {
// (1) match
var matchMethod = getDeclaredMethodSuper(rule.getClass(), "match", MigrationNode.class);
matchMethod.setAccessible(true);

var getReplacementNFClass = getDeclaredMethodSuper(rule.getClass(), "getReplacementNodeFactoryClass",
MigrationNode.class, MigrationNodeMatchResult.class);
getReplacementNFClass.setAccessible(true);

// we'll need to loop through all known nodes here to extract the rules -
// probably it makes sense to integrate this into the node documentation loop,
// as we have |node| >> |migration_rule|
for (var nodeFactoryExtension : nodeFactoryExtensions) {
var migrationNodeMock = Mockito.mock(MigrationNode.class);
var factoryClass = nodeFactoryExtension.getFactory().getClass();
Mockito.when(migrationNodeMock.getOriginalNodeFactoryClass()).then(invocation -> factoryClass);
Mockito.when(migrationNodeMock.getOriginalNodeFactoryClassName())
.then(invocation -> factoryClass.getName());

var matchResult = (MigrationNodeMatchResult) matchMethod.invoke(rule,
new Object[] { migrationNodeMock });
var nodeActions = matchResult.getNodeActions();
if (nodeActions.size() == 1) {
LOGGER.info(String.format("Rule %s returned %s actions for original %s",
rule.getClass().getName(), nodeActions.size(), factoryClass.getName()));

// (2) replacement
@SuppressWarnings("unchecked")
var replacementFactoryClass = (Class<? extends NodeFactory<?>>) getReplacementNFClass
.invoke(rule, new Object[] { migrationNodeMock, matchResult });

migrationRuleInfos.add(new MigrationRuleDocBuilder() //
.setOriginalNodeFactoryClass(factoryClass.getName()) //
.setReplacementNodeFactoryClass(replacementFactoryClass.getName()) //
.build()); //
} else if (nodeActions.size() > 1) {
LOGGER.error(
String.format("Rule %s returned %s actions for %s - this is unexpected and unsupported",
rule.getClass().getName(), factoryClass.getName(), nodeActions.size()));
}
}

} catch (Exception e) {
Throwable unwrapped = e;
if (e instanceof InvocationTargetException ite) {
unwrapped = ite.getTargetException();
}
LOGGER.warn(String.format("Error for %s: %s", rule.getClass().getName(), unwrapped.getMessage()),
unwrapped);
}
}

return migrationRuleInfos;

}

/**
* Get a declared method on the class, or any super class.
*
* @param cl
* @param methodName
* @param parameterTypes
* @return
* @throws IllegalStateException If there is no such method.
*/
private static final Method getDeclaredMethodSuper(Class<?> cl, String methodName, Class<?>... parameterTypes) {
Method method = null;
for (;;) {
try {
method = cl.getDeclaredMethod(methodName, parameterTypes);
break;

} catch (NoSuchMethodException e) {
var superClass = cl.getSuperclass();
if (superClass == null) {
break;
}
cl = superClass;
}
}
if (method == null) {
throw new IllegalStateException(
String.format("method %s is not implemented by class or super classes", methodName));
}

return method;
}

private MigrationRuleExtractor() {
// ...
}

}
Loading

0 comments on commit e831e8c

Please sign in to comment.