diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/resources/FontRegistryTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/resources/FontRegistryTest.java index c96900c0d5f..7aa7f7dfcd0 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/resources/FontRegistryTest.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/resources/FontRegistryTest.java @@ -17,17 +17,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.eclipse.core.runtime.Platform.OS; +import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; @@ -122,4 +128,175 @@ private static void waitForDisplayInstantiation(AtomicReference display } } + @Test + public void defaultFont_isStableAcrossLookupsOfOtherNames() { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + + Font defaultFont = fontRegistry.get(JFaceResources.DEFAULT_FONT); + fontRegistry.get("myfont"); + fontRegistry.get("neverRegisteredName"); + + assertSame(defaultFont, fontRegistry.get(JFaceResources.DEFAULT_FONT)); + } + + @Test + public void get_fontFromNonUIThreadFallback_doesNotOverwriteDefaultFont() throws Throwable { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + Font defaultFont = fontRegistry.get(JFaceResources.DEFAULT_FONT); + + AtomicReference fontFromNonUIThread = new AtomicReference<>(); + AtomicReference failureFromNonUIThread = new AtomicReference<>(); + Thread nonUiThread = new Thread(() -> { + try { + fontFromNonUIThread.set(fontRegistry.get("myfont")); + } catch (Throwable t) { + failureFromNonUIThread.set(t); + } + }); + nonUiThread.start(); + nonUiThread.join(); + + if (failureFromNonUIThread.get() != null) { + throw failureFromNonUIThread.get(); + } + assertSame(defaultFont, fontFromNonUIThread.get()); + assertSame(defaultFont, fontRegistry.get(JFaceResources.DEFAULT_FONT)); + } + + @Test + public void get_returnsSameFontInstanceOnRepeatedCalls() { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + + Font first = fontRegistry.get("myfont"); + Font second = fontRegistry.get("myfont"); + + assertSame(first, second); + } + + @Test + public void get_forNameThatWasNeverRegistered_returnsDefaultFontAndIsStableAfterwards() { + FontRegistry fontRegistry = new FontRegistry(); + + Font first = fontRegistry.get("neverRegisteredName"); + Font second = fontRegistry.get("neverRegisteredName"); + + assertSame(fontRegistry.get(JFaceResources.DEFAULT_FONT), first); + assertSame(first, second); + } + + @Test + public void getBoldAndGetItalic_returnSameInstanceOnRepeatedCalls() { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + + assertSame(fontRegistry.getBold("myfont"), fontRegistry.getBold("myfont")); + assertSame(fontRegistry.getItalic("myfont"), fontRegistry.getItalic("myfont")); + } + + @Test + public void put_firesPropertyChangeOnlyWhenDataActuallyChanges() { + FontRegistry fontRegistry = new FontRegistry(); + List events = new ArrayList<>(); + fontRegistry.addListener(events::add); + + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + assertEquals(1, events.size()); + assertEquals("myfont", events.get(0).getProperty()); + + // re-putting the same data must not fire a change + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + assertEquals(1, events.size()); + + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 18, SWT.NORMAL) }); + assertEquals(2, events.size()); + } + + @Test + public void put_withNewData_disposesOldFontOnlyOnDisplayDispose() { + assumeTrue(OS.isWindows(), "multiple Display instance only allowed on Windows"); + + FontRegistry fontRegistry = new FontRegistry(); + Display secondDisplay = initializeDisplayInSeparateThread(); + try { + Font original = secondDisplay.syncCall(() -> { + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + return fontRegistry.get("myfont"); + }); + + secondDisplay + .syncExec(() -> fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 18, SWT.BOLD) })); + // the font may still be in use elsewhere, so it must not be disposed right away + assertFalse(original.isDisposed(), "previous font must stay usable until its display is disposed"); + + secondDisplay.syncExec(secondDisplay::dispose); + assertTrue(original.isDisposed(), "stale font must be disposed once its display is disposed"); + } finally { + if (!secondDisplay.isDisposed()) { + secondDisplay.syncExec(secondDisplay::dispose); + } + } + } + + @Test + public void put_withNewData_invalidatesCachedBoldAndItalicFonts() { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + Font originalBold = fontRegistry.getBold("myfont"); + Font originalItalic = fontRegistry.getItalic("myfont"); + + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 18, SWT.NORMAL) }); + + Font updatedBold = fontRegistry.getBold("myfont"); + Font updatedItalic = fontRegistry.getItalic("myfont"); + + assertNotEquals(originalBold, updatedBold); + assertNotEquals(originalItalic, updatedItalic); + assertEquals(18, updatedBold.getFontData()[0].getHeight()); + assertEquals(18, updatedItalic.getFontData()[0].getHeight()); + } + + @Test + public void put_withNewData_invalidatesPreviouslyCachedFont() { + FontRegistry fontRegistry = new FontRegistry(); + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + Font original = fontRegistry.get("myfont"); + + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 18, SWT.BOLD) }); + Font updated = fontRegistry.get("myfont"); + + assertNotEquals(original, updated); + assertEquals(18, updated.getFontData()[0].getHeight()); + } + + @Test + public void hasValueForAndGetKeySet_reflectOnlyRegisteredNames() { + FontRegistry fontRegistry = new FontRegistry(); + assertFalse(fontRegistry.hasValueFor("myfont")); + assertFalse(fontRegistry.getKeySet().contains("myfont")); + + fontRegistry.put("myfont", new FontData[] { new FontData("Arial", 12, SWT.NORMAL) }); + assertTrue(fontRegistry.hasValueFor("myfont")); + assertTrue(fontRegistry.getKeySet().contains("myfont")); + + // falling back to the default for an unregistered name must not register it + fontRegistry.get("neverRegisteredName"); + assertFalse(fontRegistry.hasValueFor("neverRegisteredName")); + assertFalse(fontRegistry.getKeySet().contains("neverRegisteredName")); + } + + @Test + public void getFontDataAndGetDescriptor_fallBackToDefaultForUnregisteredName() { + FontRegistry fontRegistry = new FontRegistry(); + + assertArrayEquals(fontRegistry.getFontData(JFaceResources.DEFAULT_FONT), + fontRegistry.getFontData("neverRegisteredName")); + + FontDescriptor defaultDescriptor = fontRegistry.getDescriptor(JFaceResources.DEFAULT_FONT); + FontDescriptor fallbackDescriptor = fontRegistry.getDescriptor("neverRegisteredName"); + assertEquals(defaultDescriptor, fallbackDescriptor); + } + }