diff --git a/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/ClassInstanceCreationVisitorTest.java b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/ClassInstanceCreationVisitorTest.java new file mode 100644 index 000000000..209150756 --- /dev/null +++ b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/ClassInstanceCreationVisitorTest.java @@ -0,0 +1,530 @@ +/******************************************************************************* + * Copyright (c) 2026 Carsten Hammer. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Carsten Hammer + *******************************************************************************/ +package org.sandbox.jdt.ui.tests.quickfix; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.internal.corext.dom.ASTNodes; +import org.junit.jupiter.api.Test; +import org.sandbox.jdt.internal.common.ASTProcessor; +import org.sandbox.jdt.internal.common.AstProcessorBuilder; +import org.sandbox.jdt.internal.common.HelperVisitor; +import org.sandbox.jdt.internal.common.ReferenceHolder; +import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; +import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + +/** + * Tests for verifying that LambdaASTVisitor correctly finds ALL ClassInstanceCreation nodes. + * + *

This test class addresses issues identified in PR #593 where the ClassInstanceCreation + * visitor was only finding some instances instead of all instances in the code.

+ * + *

Key Testing Areas

+ * + * + * @author Carsten Hammer + */ +public class ClassInstanceCreationVisitorTest { + + /** + * Helper method to create a CompilationUnit from source code. + * + * @param code the Java source code + * @param name the unit name + * @return the parsed CompilationUnit + */ + private static CompilationUnit createUnit(String code, String name) { + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_17, options); + parser.setCompilerOptions(options); + parser.setEnvironment(new String[]{}, new String[]{}, null, true); + parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + parser.setUnitName(name); + parser.setSource(code.toCharArray()); + return (CompilationUnit) parser.createAST(null); + } + + /** + * Test that the visitor finds all ClassInstanceCreation nodes in a simple method. + * This is the most basic test case - multiple instances in a linear flow. + */ + @Test + public void testFindAllClassInstanceCreationInMethod() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + monitor.beginTask("Task", 100); + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + MockProgressMonitor sub3 = new MockSubProgressMonitor(monitor, 20); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(3, found.size(), "Should find all 3 ClassInstanceCreation nodes"); + } + + /** + * Test that the visitor finds ClassInstanceCreation nodes with type filtering. + * This tests the typeof parameter functionality. + */ + @Test + public void testFindClassInstanceCreationWithTypeFilter() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + monitor.beginTask("Task", 100); + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + String s = new String("test"); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List foundFiltered = new ArrayList<>(); + List foundAll = new ArrayList<>(); + Set nodesprocessed = null; + + // First find all without filter + HelperVisitor, String, Object> hvAll = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + hvAll.addClassInstanceCreation((node, holder) -> { + foundAll.add(node); + return true; + }); + hvAll.build(cu); + + // Then find only MockSubProgressMonitor instances + HelperVisitor, String, Object> hvFiltered = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + hvFiltered.addClassInstanceCreation(MockSubProgressMonitor.class, (node, holder) -> { + foundFiltered.add(node); + return true; + }); + hvFiltered.build(cu); + + assertEquals(3, foundAll.size(), "Should find 3 total ClassInstanceCreation nodes (2 MockSubProgressMonitor + 1 String)"); + assertEquals(2, foundFiltered.size(), "Should find only 2 MockSubProgressMonitor instances when filtered"); + } + + /** + * Test ClassInstanceCreation in nested blocks (if, while, for loops). + * This ensures the visitor properly traverses nested structures. + */ + @Test + public void testFindClassInstanceCreationInNestedBlocks() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + monitor.beginTask("Task", 100); + + if (true) { + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 10); + } + + while (false) { + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 20); + } + + for (int i = 0; i < 5; i++) { + MockProgressMonitor sub3 = new MockSubProgressMonitor(monitor, 30); + } + + MockProgressMonitor sub4 = new MockSubProgressMonitor(monitor, 40); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(4, found.size(), "Should find all 4 ClassInstanceCreation nodes including those in nested blocks"); + } + + /** + * Test ClassInstanceCreation inside lambdas. + * This ensures the visitor traverses into lambda expressions. + */ + @Test + public void testFindClassInstanceCreationInLambdas() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + Runnable r = () -> { + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + }; + + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(2, found.size(), "Should find all 2 ClassInstanceCreation nodes including one inside lambda"); + } + + /** + * Test ClassInstanceCreation in anonymous classes. + * This ensures the visitor traverses into anonymous class bodies. + */ + @Test + public void testFindClassInstanceCreationInAnonymousClasses() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + Runnable r = new Runnable() { + @Override + public void run() { + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + } + }; + + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(3, found.size(), "Should find all 3 ClassInstanceCreation nodes (1 Runnable anonymous + 2 MockSubProgressMonitor)"); + } + + /** + * Test standalone ClassInstanceCreation without any preceding method calls. + * This tests the case where there's no beginTask() before SubProgressMonitor creation. + */ + @Test + public void testFindStandaloneClassInstanceCreation() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + // No beginTask() call here + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + MockProgressMonitor sub3 = new MockSubProgressMonitor(monitor, 20); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(3, found.size(), "Should find all 3 ClassInstanceCreation nodes even without beginTask"); + } + + /** + * Test the scope function parameter and its effect on visitor traversal. + * This is CRITICAL - it tests how the navigate/scope function limits the search space. + */ + @Test + public void testScopeFunctionBehavior() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + monitor.beginTask("Task", 100); + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + + if (true) { + MockProgressMonitor sub3 = new MockSubProgressMonitor(monitor, 20); + } + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + // Test 1: Find all instances without scope limitation + List foundAll = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hvAll = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + hvAll.addClassInstanceCreation((node, holder) -> { + foundAll.add(node); + return true; + }); + hvAll.build(cu); + + assertEquals(3, foundAll.size(), "Without scope function, should find all 3 ClassInstanceCreation nodes"); + + // Test 2: Use ASTProcessor with scope function that navigates to Block + // This mimics the JFace plugin pattern + List foundWithScope = new ArrayList<>(); + ReferenceHolder dataholder = new ReferenceHolder<>(); + + AstProcessorBuilder.with(dataholder, nodesprocessed) + .processor() + .callMethodInvocationVisitor(MockProgressMonitor.class, "beginTask", (node, holder) -> { + holder.put("beginTask", node); + return true; + }, s -> ASTNodes.getTypedAncestor(s, Block.class)) + .callClassInstanceCreationVisitor((node, holder) -> { + foundWithScope.add(node); + return true; + }) + .build(cu); + + // After navigating to Block containing beginTask, we should find ALL ClassInstanceCreation + // nodes in that Block (including the one in the nested if block) + assertTrue(foundWithScope.size() > 0, "With scope function, should find ClassInstanceCreation nodes in the Block scope"); + assertEquals(foundAll.size(), foundWithScope.size(), + "Scope function should find all nodes when the Block contains all instances"); + } + + /** + * Test the chained visitor pattern similar to JFace plugin. + * This reproduces the exact pattern used in JFacePlugin.java to find the issue. + */ + @Test + public void testJFacePluginPattern() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + monitor.beginTask("Task", 100); + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 30); + MockProgressMonitor sub3 = new MockSubProgressMonitor(monitor, 20); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + // Track how many beginTask and ClassInstanceCreation nodes we find + List beginTaskNodes = new ArrayList<>(); + List cicNodes = new ArrayList<>(); + Set nodesprocessed = null; + ReferenceHolder dataholder = new ReferenceHolder<>(); + + AstProcessorBuilder.with(dataholder, nodesprocessed) + .processor() + .callMethodInvocationVisitor(MockProgressMonitor.class, "beginTask", (node, holder) -> { + beginTaskNodes.add(node); + holder.put("beginTask", node); + return true; + }, s -> ASTNodes.getTypedAncestor(s, Block.class)) + .callClassInstanceCreationVisitor(MockSubProgressMonitor.class, (node, holder) -> { + cicNodes.add(node); + return true; + }) + .build(cu); + + assertEquals(1, beginTaskNodes.size(), "Should find 1 beginTask invocation"); + assertEquals(3, cicNodes.size(), "Should find all 3 MockSubProgressMonitor instances after beginTask"); + } + + /** + * Test with multiple blocks and verify scope behavior. + * This tests what happens when ClassInstanceCreation nodes are in different blocks. + */ + @Test + public void testMultipleBlocksWithSeparateInstances() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void method1(MockProgressMonitor monitor) { + monitor.beginTask("Task1", 50); + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + } + + public void method2(MockProgressMonitor monitor) { + monitor.beginTask("Task2", 50); + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 50); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + // Find all without scope + List foundAll = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + hv.addClassInstanceCreation((node, holder) -> { + foundAll.add(node); + return true; + }); + hv.build(cu); + + assertEquals(2, foundAll.size(), "Should find both ClassInstanceCreation nodes in different methods"); + } + + /** + * Test edge case: deeply nested ClassInstanceCreation. + * This ensures the visitor traverses arbitrarily deep structures. + */ + @Test + public void testDeeplyNestedClassInstanceCreation() { + String code = """ + package test; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockProgressMonitor; + import org.sandbox.jdt.ui.tests.quickfix.mock.MockSubProgressMonitor; + + public class Test { + public void doWork(MockProgressMonitor monitor) { + if (true) { + if (true) { + while (false) { + for (int i = 0; i < 1; i++) { + MockProgressMonitor sub1 = new MockSubProgressMonitor(monitor, 50); + } + } + } + } + MockProgressMonitor sub2 = new MockSubProgressMonitor(monitor, 50); + } + } + """; + + CompilationUnit cu = createUnit(code, "Test"); + + List found = new ArrayList<>(); + Set nodesprocessed = null; + HelperVisitor, String, Object> hv = + new HelperVisitor<>(nodesprocessed, new ReferenceHolder<>()); + + hv.addClassInstanceCreation((node, holder) -> { + found.add(node); + return true; + }); + + hv.build(cu); + + assertEquals(2, found.size(), "Should find both ClassInstanceCreation nodes including deeply nested one"); + } +} diff --git a/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockProgressMonitor.java b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockProgressMonitor.java new file mode 100644 index 000000000..a93506b04 --- /dev/null +++ b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockProgressMonitor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2026 Carsten Hammer. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Carsten Hammer + *******************************************************************************/ +package org.sandbox.jdt.ui.tests.quickfix.mock; + +/** + * Mock interface for testing ClassInstanceCreation visitor patterns. + * Mimics the IProgressMonitor pattern without JFace dependencies. + */ +public interface MockProgressMonitor { + /** + * Begins a task with the specified name and total work units. + * + * @param name the task name + * @param totalWork the total work units + */ + void beginTask(String name, int totalWork); + + /** + * Marks the task as complete. + */ + void done(); + + /** + * Reports work progress. + * + * @param work the amount of work done + */ + void worked(int work); +} diff --git a/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubMonitor.java b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubMonitor.java new file mode 100644 index 000000000..d531a2cb1 --- /dev/null +++ b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubMonitor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2026 Carsten Hammer. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Carsten Hammer + *******************************************************************************/ +package org.sandbox.jdt.ui.tests.quickfix.mock; + +/** + * Mock class for testing ClassInstanceCreation visitor patterns. + * Mimics the SubMonitor pattern (replacement for SubProgressMonitor) without JFace dependencies. + */ +public class MockSubMonitor implements MockProgressMonitor { + + /** + * Converts a monitor to a SubMonitor. + * + * @param monitor the monitor to convert + * @param work the total work units + * @return a new MockSubMonitor instance + */ + public static MockSubMonitor convert(MockProgressMonitor monitor, int work) { + return new MockSubMonitor(); + } + + /** + * Converts a monitor to a SubMonitor with a task name. + * + * @param monitor the monitor to convert + * @param taskName the task name + * @param work the total work units + * @return a new MockSubMonitor instance + */ + public static MockSubMonitor convert(MockProgressMonitor monitor, String taskName, int work) { + return new MockSubMonitor(); + } + + /** + * Splits off a portion of work. + * + * @param work the amount of work to split + * @return a new MockSubMonitor for the split work + */ + public MockSubMonitor split(int work) { + return new MockSubMonitor(); + } + + @Override + public void beginTask(String name, int totalWork) { + // Mock implementation + } + + @Override + public void done() { + // Mock implementation + } + + @Override + public void worked(int work) { + // Mock implementation + } +} diff --git a/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubProgressMonitor.java b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubProgressMonitor.java new file mode 100644 index 000000000..c17005c00 --- /dev/null +++ b/sandbox_common_test/src/org/sandbox/jdt/ui/tests/quickfix/mock/MockSubProgressMonitor.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2026 Carsten Hammer. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Carsten Hammer + *******************************************************************************/ +package org.sandbox.jdt.ui.tests.quickfix.mock; + +/** + * Mock class for testing ClassInstanceCreation visitor patterns. + * Mimics the deprecated SubProgressMonitor pattern without JFace dependencies. + */ +public class MockSubProgressMonitor implements MockProgressMonitor { + + private final MockProgressMonitor monitor; + private final int ticks; + private final int style; + + /** + * Creates a new MockSubProgressMonitor with 2 arguments. + * + * @param monitor the parent monitor + * @param ticks the number of ticks + */ + public MockSubProgressMonitor(MockProgressMonitor monitor, int ticks) { + this(monitor, ticks, 0); + } + + /** + * Creates a new MockSubProgressMonitor with 3 arguments. + * + * @param monitor the parent monitor + * @param ticks the number of ticks + * @param style the style flags + */ + public MockSubProgressMonitor(MockProgressMonitor monitor, int ticks, int style) { + this.monitor = monitor; + this.ticks = ticks; + this.style = style; + } + + @Override + public void beginTask(String name, int totalWork) { + // Mock implementation + } + + @Override + public void done() { + // Mock implementation + } + + @Override + public void worked(int work) { + // Mock implementation + } +} diff --git a/sandbox_jface_cleanup/README.md b/sandbox_jface_cleanup/README.md index 29b0c5f6e..7a21ec3b9 100644 --- a/sandbox_jface_cleanup/README.md +++ b/sandbox_jface_cleanup/README.md @@ -9,7 +9,8 @@ The **JFace Cleanup** plugin modernizes Eclipse JFace code by migrating deprecat ## Key Features - 🔄 **SubProgressMonitor → SubMonitor** - Automatic migration to modern progress API -- 🎯 **Style Flag Handling** - Properly converts `PREPEND_MAIN_LABEL_TO_SUBTASK` flag +- 🎯 **Style Flag Mapping** - Maps `SUPPRESS_SUBTASK_LABEL` to `SUPPRESS_SUBTASK` +- 🗑️ **Flag Dropping** - Removes `PREPEND_MAIN_LABEL_TO_SUBTASK` (no SubMonitor equivalent) - 📦 **Variable Name Management** - Generates unique variable names to avoid conflicts - ♻️ **Idempotent** - Running cleanup multiple times produces the same result - 🔌 **Eclipse Integration** - Works seamlessly with Eclipse RCP/JFace code @@ -27,52 +28,77 @@ The **JFace Cleanup** plugin modernizes Eclipse JFace code by migrating deprecat **Basic Transformation:** ```java // Before -SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 100); +monitor.beginTask("Task", 100); +IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 50); // After -SubMonitor subMonitor = SubMonitor.convert(monitor, 100); +SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); +IProgressMonitor sub = subMonitor.split(50); ``` -**With Style Flags:** +**With SUPPRESS_SUBTASK_LABEL Flag:** ```java // Before +monitor.beginTask("Task", 100); SubProgressMonitor sub = new SubProgressMonitor( - monitor, 50, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK); + monitor, 50, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); // After -SubMonitor sub = SubMonitor.convert(monitor, 50) - .setWorkRemaining(50); +SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); +IProgressMonitor sub = subMonitor.split(50, SubMonitor.SUPPRESS_SUBTASK); +``` + +**With PREPEND_MAIN_LABEL_TO_SUBTASK Flag (Dropped):** +```java +// Before +monitor.beginTask("Task", 100); +SubProgressMonitor sub = new SubProgressMonitor( + monitor, 50, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK); + +// After - flag is dropped as there's no equivalent in SubMonitor +SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); +IProgressMonitor sub = subMonitor.split(50); ``` **Unique Variable Names:** ```java // Before -SubProgressMonitor sub = new SubProgressMonitor(monitor, 100); -// ... later in code ... -SubProgressMonitor sub = new SubProgressMonitor(otherMonitor, 50); +String subMonitor = "test"; +monitor.beginTask("Task", 100); +IProgressMonitor sub = new SubProgressMonitor(monitor, 50); // After -SubMonitor sub = SubMonitor.convert(monitor, 100); -// ... later in code ... -SubMonitor sub2 = SubMonitor.convert(otherMonitor, 50); // Unique name generated +String subMonitor = "test"; +SubMonitor subMonitor2 = SubMonitor.convert(monitor, "Task", 100); +IProgressMonitor sub = subMonitor2.split(50); // Unique name generated ``` ## Migration Pattern -The cleanup transforms: +The cleanup transforms `beginTask` + `SubProgressMonitor` to `SubMonitor.convert` + `split`: ``` +monitor.beginTask(msg, work); new SubProgressMonitor(monitor, ticks) ↓ -SubMonitor.convert(monitor, ticks) +SubMonitor subMonitor = SubMonitor.convert(monitor, msg, work); +subMonitor.split(ticks) +``` + +With `SUPPRESS_SUBTASK_LABEL` flag: + +``` +new SubProgressMonitor(monitor, ticks, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL) + ↓ (in beginTask context) +subMonitor.split(ticks, SubMonitor.SUPPRESS_SUBTASK) ``` -With style flag `PREPEND_MAIN_LABEL_TO_SUBTASK`: +With `PREPEND_MAIN_LABEL_TO_SUBTASK` flag: ``` new SubProgressMonitor(monitor, ticks, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK) - ↓ -SubMonitor.convert(monitor, ticks).setWorkRemaining(ticks) + ↓ (flag is dropped - no equivalent in SubMonitor) +subMonitor.split(ticks) ``` ## Why Migrate? @@ -137,8 +163,9 @@ xvfb-run --auto-servernum mvn test -pl sandbox_jface_cleanup_test ## Limitations -- Does not handle complex style flag combinations (only `PREPEND_MAIN_LABEL_TO_SUBTASK`) +- Combined flag expressions using bitwise OR (e.g., `FLAG1 | FLAG2`) or numeric flag literals are not automatically mapped and require manual review - Custom SubProgressMonitor subclasses require manual review +- Only handles SubProgressMonitor instances with a corresponding beginTask call in the same scope - Some rare edge cases may need manual adjustment See [TODO.md](TODO.md) for planned improvements. diff --git a/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/JfaceCleanUpFixCore.java b/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/JfaceCleanUpFixCore.java index 3eb374283..987bc8425 100644 --- a/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/JfaceCleanUpFixCore.java +++ b/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/JfaceCleanUpFixCore.java @@ -75,7 +75,19 @@ public void rewriteASTInternal(final CompilationUnitRewrite cuRewrite, final Lin } else { rangeComputer= new TightSourceRangeComputer(); } - rangeComputer.addTightSourceNode( hit.get(0).minv); + + // Get the first MonitorHolder from the hit map (key might not be 0 due to scope grouping) + MonitorHolder mh = hit.values().stream().findFirst().orElse(null); + if (mh != null) { + // For standalone SubProgressMonitor, use the ClassInstanceCreation node instead of minv + if (mh.minv != null) { + rangeComputer.addTightSourceNode(mh.minv); + } else if (!mh.setofcic.isEmpty()) { + // Use the first SubProgressMonitor creation for standalone case + rangeComputer.addTightSourceNode(mh.setofcic.iterator().next()); + } + } + rewrite.setTargetSourceRangeComputer(rangeComputer); jfacefound.rewrite(JfaceCleanUpFixCore.this, hit, cuRewrite, group); } diff --git a/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/helper/JFacePlugin.java b/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/helper/JFacePlugin.java index 10d0873f1..5874b3c3c 100644 --- a/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/helper/JFacePlugin.java +++ b/sandbox_jface_cleanup/src/org/sandbox/jdt/internal/corext/fix/helper/JFacePlugin.java @@ -33,10 +33,13 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; @@ -188,11 +191,6 @@ public void find(JfaceCleanUpFixCore fixcore, CompilationUnit compilationUnit, return true; }, s -> ASTNodes.getTypedAncestor(s, Block.class)) .callClassInstanceCreationVisitor(SubProgressMonitor.class, (node, holder) -> { - // Guard against empty holder - if (holder.isEmpty()) { - return true; - } - MonitorHolder mh = holder.get(holder.size() - 1); List arguments = node.arguments(); if (arguments.isEmpty()) { return true; @@ -208,15 +206,23 @@ public void find(JfaceCleanUpFixCore fixcore, CompilationUnit compilationUnit, firstArgName = sn.getIdentifier(); } - if (firstArgName == null || !mh.minvname.equals(firstArgName)) { - return true; + // Check if this SubProgressMonitor is associated with a beginTask + if (!holder.isEmpty() && firstArgName != null) { + MonitorHolder mh = holder.get(holder.size() - 1); + if (mh.minvname.equals(firstArgName)) { + logDebug("Found SubProgressMonitor construction at position " + node.getStartPosition() + " for variable '" + firstArgName + "' with beginTask"); //$NON-NLS-1$ //$NON-NLS-2$ + mh.setofcic.add(node); + } } - logDebug("Found SubProgressMonitor construction at position " + node.getStartPosition() + " for variable '" + firstArgName + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - mh.setofcic.add(node); - operations.add(fixcore.rewrite(holder)); + return true; - }) + }, s -> ASTNodes.getTypedAncestor(s, Block.class)) .build(compilationUnit); + + // Add operations for beginTask-associated monitors + if (!dataholder.isEmpty()) { + operations.add(fixcore.rewrite(dataholder)); + } } /** @@ -263,6 +269,8 @@ public void rewrite(JfaceCleanUpFixCore upp, final ReferenceHolder entry : hit.entrySet()) { MonitorHolder mh = entry.getValue(); + + // Handle beginTask + SubProgressMonitor pattern MethodInvocation minv = mh.minv; // Generate unique identifier name for SubMonitor variable @@ -289,12 +297,7 @@ public void rewrite(JfaceCleanUpFixCore upp, final ReferenceHolder arguments = submon.arguments(); if (arguments.size() < 2) { continue; @@ -327,7 +344,11 @@ public void rewrite(JfaceCleanUpFixCore upp, final ReferenceHolder subMonitor.split(work) * * 3-arg: new SubProgressMonitor(monitor, work, flags) - * -> subMonitor.split(work, flags) + * -> subMonitor.split(work, mappedFlags) + * + * Flag mapping: + * - SUPPRESS_SUBTASK_LABEL -> SUPPRESS_SUBTASK + * - PREPEND_MAIN_LABEL_TO_SUBTASK -> dropped (no equivalent) */ MethodInvocation newMethodInvocation2 = ast.newMethodInvocation(); newMethodInvocation2.setName(ast.newSimpleName("split")); //$NON-NLS-1$ @@ -340,8 +361,13 @@ public void rewrite(JfaceCleanUpFixCore upp, final ReferenceHolder= 3) { - ASTNode flagsArg = (ASTNode) arguments.get(2); - splitCallArguments.add(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(flagsArg))); + Expression flagsArg = (Expression) arguments.get(2); + Expression mappedFlag = mapSubProgressMonitorFlags(flagsArg, ast, cuRewrite); + + // Only add the flag if it wasn't dropped (PREPEND_MAIN_LABEL_TO_SUBTASK is dropped) + if (mappedFlag != null) { + splitCallArguments.add(mappedFlag); + } } ASTNodes.replaceButKeepComment(rewrite, submon, newMethodInvocation2, group); @@ -379,6 +405,63 @@ private String generateUniqueVariableName(ASTNode node, String baseName) { return candidate; } + /** + * Maps SubProgressMonitor flags to SubMonitor flags. + * + *

Flag mappings:

+ *
    + *
  • {@code SubProgressMonitor.SUPPRESS_SUBTASK_LABEL} → {@code SubMonitor.SUPPRESS_SUBTASK}
  • + *
  • {@code SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK} → removed (no equivalent)
  • + *
+ * + *

Limitations: This method only handles single flag constants. Combined flag expressions + * using bitwise OR (e.g., {@code FLAG1 | FLAG2}) or numeric literals are not mapped and will be + * passed through unchanged, which may result in incorrect behavior. Such cases require manual review.

+ * + * @param flagExpr the original flag expression from SubProgressMonitor constructor + * @param ast the AST to create new nodes + * @param cuRewrite the compilation unit rewrite context + * @return the mapped flag expression for SubMonitor, or null if flag should be dropped + */ + private Expression mapSubProgressMonitorFlags(Expression flagExpr, AST ast, CompilationUnitRewrite cuRewrite) { + // Handle field access: SubProgressMonitor.SUPPRESS_SUBTASK_LABEL + if (flagExpr instanceof QualifiedName) { + QualifiedName qn = (QualifiedName) flagExpr; + String fieldName = qn.getName().getIdentifier(); + + if ("SUPPRESS_SUBTASK_LABEL".equals(fieldName)) { //$NON-NLS-1$ + // Map to SubMonitor.SUPPRESS_SUBTASK + QualifiedName newFlag = ast.newQualifiedName( + ast.newSimpleName(SubMonitor.class.getSimpleName()), + ast.newSimpleName("SUPPRESS_SUBTASK")); //$NON-NLS-1$ + return newFlag; + } else if ("PREPEND_MAIN_LABEL_TO_SUBTASK".equals(fieldName)) { //$NON-NLS-1$ + // Drop this flag - no equivalent in SubMonitor + return null; + } + } + + // Handle FieldAccess syntax (e.g., expression.FIELD_NAME) + if (flagExpr instanceof FieldAccess) { + FieldAccess fa = (FieldAccess) flagExpr; + String fieldName = fa.getName().getIdentifier(); + + if ("SUPPRESS_SUBTASK_LABEL".equals(fieldName)) { //$NON-NLS-1$ + // Map to SubMonitor.SUPPRESS_SUBTASK + FieldAccess newFlag = ast.newFieldAccess(); + newFlag.setExpression(ast.newSimpleName(SubMonitor.class.getSimpleName())); + newFlag.setName(ast.newSimpleName("SUPPRESS_SUBTASK")); //$NON-NLS-1$ + return newFlag; + } else if ("PREPEND_MAIN_LABEL_TO_SUBTASK".equals(fieldName)) { //$NON-NLS-1$ + // Drop this flag - no equivalent in SubMonitor + return null; + } + } + + // For other expressions (constants, variables), pass through unchanged + return ASTNodes.createMoveTarget(cuRewrite.getASTRewrite(), ASTNodes.getUnparenthesedExpression(flagExpr)); + } + @Override public String getPreview(boolean afterRefactoring) { if (!afterRefactoring) { diff --git a/sandbox_jface_cleanup_test/pom.xml b/sandbox_jface_cleanup_test/pom.xml index c038a8741..578b0cbf0 100644 --- a/sandbox_jface_cleanup_test/pom.xml +++ b/sandbox_jface_cleanup_test/pom.xml @@ -26,7 +26,6 @@ ${tycho-version} BREE - false true diff --git a/sandbox_jface_cleanup_test/src/org/sandbox/jdt/ui/tests/quickfix/Java8CleanUpTest.java b/sandbox_jface_cleanup_test/src/org/sandbox/jdt/ui/tests/quickfix/Java8CleanUpTest.java index 62e651772..edc34a82f 100644 --- a/sandbox_jface_cleanup_test/src/org/sandbox/jdt/ui/tests/quickfix/Java8CleanUpTest.java +++ b/sandbox_jface_cleanup_test/src/org/sandbox/jdt/ui/tests/quickfix/Java8CleanUpTest.java @@ -76,7 +76,7 @@ public void createPackageFragmentRoot(IProgressMonitor monitor) throws CoreExcep import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages; public class Test extends ArrayList { public void createPackageFragmentRoot(IProgressMonitor monitor) throws CoreException, InterruptedException { - SubMonitor subMonitor=SubMonitor.convert(monitor,NewWizardMessages.NewSourceFolderWizardPage_operation,3); + SubMonitor subMonitor = SubMonitor.convert(monitor, NewWizardMessages.NewSourceFolderWizardPage_operation, 3); IProgressMonitor subProgressMonitor= subMonitor.split(1); IProgressMonitor subProgressMonitor2= subMonitor.split(2); } @@ -112,12 +112,12 @@ public void createPackageFragmentRoot2(IProgressMonitor monitor) throws CoreExce import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages; public class Test extends ArrayList { public void createPackageFragmentRoot(IProgressMonitor monitor) throws CoreException, InterruptedException { - SubMonitor subMonitor=SubMonitor.convert(monitor,NewWizardMessages.NewSourceFolderWizardPage_operation,3); + SubMonitor subMonitor = SubMonitor.convert(monitor, NewWizardMessages.NewSourceFolderWizardPage_operation, 3); IProgressMonitor subProgressMonitor= subMonitor.split(1); IProgressMonitor subProgressMonitor2= subMonitor.split(2); } public void createPackageFragmentRoot2(IProgressMonitor monitor) throws CoreException, InterruptedException { - SubMonitor subMonitor=SubMonitor.convert(monitor,NewWizardMessages.NewSourceFolderWizardPage_operation,3); + SubMonitor subMonitor = SubMonitor.convert(monitor, NewWizardMessages.NewSourceFolderWizardPage_operation, 3); IProgressMonitor subProgressMonitor3= subMonitor.split(1); IProgressMonitor subProgressMonitor4= subMonitor.split(2); } @@ -141,7 +141,7 @@ public void doWork(IProgressMonitor monitor) { import org.eclipse.core.runtime.SubMonitor; public class Test { public void doWork(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Task",100); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); IProgressMonitor sub= subMonitor.split(50, 1); } } @@ -166,7 +166,7 @@ public void doWork(IProgressMonitor monitor) { public class Test { public void doWork(IProgressMonitor monitor) { String subMonitor = "test"; - SubMonitor subMonitor2=SubMonitor.convert(monitor,"Task",100); + SubMonitor subMonitor2 = SubMonitor.convert(monitor, "Task", 100); IProgressMonitor sub= subMonitor2.split(50); } } @@ -178,9 +178,9 @@ public void doWork(IProgressMonitor monitor) { import org.eclipse.core.runtime.SubMonitor; public class Test { public void doWork(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Task",100); - IProgressMonitor sub= subMonitor.split(50); - IProgressMonitor sub2= subMonitor.split(30); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); + IProgressMonitor sub = subMonitor.split(50); + IProgressMonitor sub2 = subMonitor.split(30); } } """, //$NON-NLS-1$ @@ -190,9 +190,9 @@ public void doWork(IProgressMonitor monitor) { import org.eclipse.core.runtime.SubMonitor; public class Test { public void doWork(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Task",100); - IProgressMonitor sub= subMonitor.split(50); - IProgressMonitor sub2= subMonitor.split(30); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); + IProgressMonitor sub = subMonitor.split(50); + IProgressMonitor sub2 = subMonitor.split(30); } } """), //$NON-NLS-1$ @@ -205,8 +205,8 @@ public void doWork(IProgressMonitor monitor) { public class Test { // This method already uses SubMonitor - should not be modified public void alreadyConverted(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Already converted",50); - IProgressMonitor sub= subMonitor.split(25); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Already converted", 50); + IProgressMonitor sub = subMonitor.split(25); } // This method still uses SubProgressMonitor - should be converted public void needsConversion(IProgressMonitor monitor) { @@ -222,12 +222,12 @@ public void needsConversion(IProgressMonitor monitor) { public class Test { // This method already uses SubMonitor - should not be modified public void alreadyConverted(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Already converted",50); - IProgressMonitor sub= subMonitor.split(25); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Already converted", 50); + IProgressMonitor sub = subMonitor.split(25); } // This method still uses SubProgressMonitor - should be converted public void needsConversion(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Needs conversion",100); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Needs conversion", 100); IProgressMonitor sub= subMonitor.split(60); } } @@ -256,12 +256,12 @@ public void innerMethod(IProgressMonitor monitor) { import org.eclipse.core.runtime.SubMonitor; public class Test { public void outerMethod(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Outer task",50); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Outer task", 50); IProgressMonitor sub= subMonitor.split(25); } class InnerClass { public void innerMethod(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Inner task",100); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Inner task", 100); IProgressMonitor sub= subMonitor.split(50); } } @@ -291,12 +291,60 @@ public void withLambda(IProgressMonitor monitor) { public class Test { public void withLambda(IProgressMonitor monitor) { Consumer task = m -> { - SubMonitor subMonitor=SubMonitor.convert(m,"Lambda task",100); + SubMonitor subMonitor = SubMonitor.convert(m, "Lambda task", 100); IProgressMonitor sub = subMonitor.split(50); }; task.accept(monitor); } } +"""), //$NON-NLS-1$ + // Test flag mapping: SUPPRESS_SUBTASK_LABEL -> SUPPRESS_SUBTASK + SuppressSubtaskLabelFlag( +""" +package test; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +public class Test { + public void doWork(IProgressMonitor monitor) { + monitor.beginTask("Task", 100); + IProgressMonitor sub = new SubProgressMonitor(monitor, 50, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); + } +} +""", //$NON-NLS-1$ +""" +package test; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +public class Test { + public void doWork(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); + IProgressMonitor sub = subMonitor.split(50, SubMonitor.SUPPRESS_SUBTASK); + } +} +"""), //$NON-NLS-1$ + // Test PREPEND_MAIN_LABEL_TO_SUBTASK flag is dropped + PrependMainLabelToSubtaskFlag( +""" +package test; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +public class Test { + public void doWork(IProgressMonitor monitor) { + monitor.beginTask("Task", 100); + IProgressMonitor sub = new SubProgressMonitor(monitor, 50, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK); + } +} +""", //$NON-NLS-1$ +""" +package test; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +public class Test { + public void doWork(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task", 100); + IProgressMonitor sub = subMonitor.split(50); + } +} """), //$NON-NLS-1$ BothImportsCoexist( """ @@ -320,7 +368,7 @@ public void doWork(IProgressMonitor monitor) { // Simulating a scenario where both imports might coexist public class Test { public void doWork(IProgressMonitor monitor) { - SubMonitor subMonitor=SubMonitor.convert(monitor,"Task with both imports",100); + SubMonitor subMonitor = SubMonitor.convert(monitor, "Task with both imports", 100); // Only Eclipse's SubProgressMonitor should be converted IProgressMonitor sub= subMonitor.split(50); } diff --git a/test_output.txt b/test_output.txt new file mode 100644 index 000000000..af607abe0 --- /dev/null +++ b/test_output.txt @@ -0,0 +1,14 @@ +[INFO] Scanning for projects... +[INFO] Tycho Version: 5.0.2 (9f65dc09a72ee08f29cc8a4d5381a45b33dc7144) +[INFO] Tycho Mode: project +[INFO] Tycho Builder: maven +[INFO] Build Threads: 1 +Downloading from local: file:///home/runner/.m2/repository/org/sandbox/sandbox_target/1.2.5-SNAPSHOT/maven-metadata.xml +Downloading from local: file:///home/runner/.m2/repository/org/sandbox/sandbox_target/1.2.5-SNAPSHOT/sandbox_target-1.2.5-SNAPSHOT.target +[ERROR] resolve target artifact org.sandbox:sandbox_target:1.2.5-SNAPSHOT:no classifier failed! Could not resolve target platform specification artifact org.sandbox:sandbox_target:target:1.2.5-SNAPSHOT: The following artifacts could not be resolved: org.sandbox:sandbox_target:target:1.2.5-SNAPSHOT (absent): Could not find artifact org.sandbox:sandbox_target:target:1.2.5-SNAPSHOT in local (file:///home/runner/.m2/repository/) -> [Help 1] +[ERROR] +[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. +[ERROR] Re-run Maven using the -X switch to enable full debug logging. +[ERROR] +[ERROR] For more information about the errors and possible solutions, please read the following articles: +[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MavenExecutionException