Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.e4.ui.workbench;singleton:=true
Bundle-Version: 1.18.300.qualifier
Bundle-Version: 1.18.400.qualifier
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,20 @@ public void resolveImports(List<MApplicationElement> imports, List<MApplicationE
element = null;
}
commands.add(() -> {
if (element != null && !feature.getEType().isInstance(element)) {
// An import that resolves to a different-typed element (e.g. two
// elements sharing an elementId) is a contribution error, not a
// reason to abort assembling the whole application model.
warn("Skipping import for feature {} on {}: resolved element {} is not assignable to the feature type", //$NON-NLS-1$
feature.getName(), target, element.getElementId());
return;
}
if (feature.isMany()) {
if (element == null) {
warn("Skipping unresolved import for many-valued feature {} on {}", //$NON-NLS-1$
feature.getName(), target);
return;
}
error("""
Replacing in {}.
Feature={}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.eclipse.e4.ui.tests.workbench;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.annotation.PostConstruct;
Expand Down Expand Up @@ -44,6 +45,7 @@
import org.eclipse.e4.ui.internal.workbench.swt.E4Application;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.application.commands.MCommand;
import org.eclipse.e4.ui.model.application.impl.ApplicationFactoryImpl;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.advanced.MArea;
Expand Down Expand Up @@ -340,6 +342,54 @@ public void testImports_noImportElementId() throws Exception {
assertEquals("Could not resolve import for null", logMessages.poll());
}

/**
* Tests that an import resolving to an element of an incompatible type (two
* elements sharing an elementId) is skipped with a warning instead of aborting
* model assembly with a {@link ClassCastException}. See
* <a href="https://github.com/eclipse-platform/eclipse.platform.ui/issues/4148">issue
* 4148</a>.
*/
@Test
public void testImports_typeIncompatibleElement() throws Exception {
List<MApplicationElement> imports = new ArrayList<>();
List<MApplicationElement> addedElements = new ArrayList<>();

final String sharedElementId = "testImports_typeIncompatible_id";

// The fragment imports a window with the shared id ...
MTrimmedWindow importWindow = modelService.createModelElement(MTrimmedWindow.class);
importWindow.setElementId(sharedElementId);
MModelFragments fragment = MFragmentFactory.INSTANCE.createModelFragments();
fragment.getImports().add(importWindow);
imports.add(importWindow);

// ... but the application only contains a command (a different type) that
// shares that id, so the id-based lookup resolves to a type-incompatible
// element for the MUIElement-typed placeholder reference.
MCommand collidingCommand = modelService.createModelElement(MCommand.class);
collidingCommand.setElementId(sharedElementId);
application.getCommands().add(collidingCommand);

MPlaceholder placeholder = modelService.createModelElement(MPlaceholder.class);
placeholder.setRef(importWindow);
addedElements.add(placeholder);

CountDownLatch countDownLatch = new CountDownLatch(1);
this.logListener.countDownLatch = countDownLatch;

// Must not throw: the incompatible resolution is skipped, the reference is
// left untouched, and every other fragment still applies.
assembler.resolveImports(imports, addedElements);

assertNotEquals(collidingCommand, placeholder.getRef());
assertEquals(importWindow, placeholder.getRef());

boolean completed = countDownLatch.await(COUNTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
assertTrue(completed, "Timeout - no event received");
assertEquals(1, logMessages.size());
assertTrue(logMessages.poll().startsWith("Skipping import for feature"));
}

/**
* Make sure that all fragments and imports are resolved before the
* post-processors are run. For reference, see
Expand Down
Loading