From 90ed1702f13a763d9b6500c169b02ef79a560b2f Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Wed, 9 Apr 2025 00:23:54 +0200 Subject: [PATCH] Merge FileImageDescriptor into URLImageDescriptor and clean it up In it's core the FileImageDescriptor queries the given context class for a resource at the given path and then operates on the returned URL or directly fetches the resource's stream. By obtaining the resource's URL immediately and using an URLImageDescriptor with it a lot of similar code and logic from the FileImageDescriptor can be saved. Additionally apply a few minor code clean-ups and remove a unused internal method. --- .../jface/resource/FileImageDescriptor.java | 313 ------------------ .../jface/resource/ImageDescriptor.java | 21 +- .../jface/resource/URLImageDescriptor.java | 38 ++- 3 files changed, 47 insertions(+), 325 deletions(-) delete mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java deleted file mode 100644 index 8fc4e967f23..00000000000 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java +++ /dev/null @@ -1,313 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. - * - * 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: - * IBM Corporation - initial API and implementation - * Christoph Läubrich - Bug 567898 - [JFace][HiDPI] ImageDescriptor support alternative naming scheme for high dpi - * Daniel Kruegler - #375, #376, #378, #396, #398, #401, - * #679: Ensure that a fresh ImageFileNameProvider instance is created to preserve Image#equals invariant. - *******************************************************************************/ -package org.eclipse.jface.resource; - -import static org.eclipse.jface.resource.URLImageDescriptor.loadImageData; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.eclipse.core.runtime.FileLocator; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Status; -import org.eclipse.jface.internal.InternalPolicy; -import org.eclipse.jface.util.Policy; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.ImageFileNameProvider; - -/** - * An image descriptor that loads its image information from a file. - */ -class FileImageDescriptor extends ImageDescriptor implements IAdaptable { - - private class ImageProvider implements ImageFileNameProvider { - - @Override - public String getImagePath(int zoom) { - final boolean logIOException = zoom == 100; - if (zoom == 100) { - return getFilePath(name, logIOException); - } - String xName = getxName(name, zoom); - if (xName != null) { - String xResult = getFilePath(xName, logIOException); - if (xResult != null) { - return xResult; - } - } - String xPath = getxPath(name, zoom); - if (xPath != null) { - String xResult = getFilePath(xPath, logIOException); - if (xResult != null) { - return xResult; - } - } - return null; - } - - } - - private static final Pattern XPATH_PATTERN = Pattern.compile("(\\d+)x(\\d+)"); //$NON-NLS-1$ - - /** - * The class whose resource directory contain the file, or null - * if none. - */ - private final Class location; - - /** - * The name of the file. - */ - private final String name; - - /** - * Creates a new file image descriptor. The file has the given file name and - * is located in the given class's resource directory. If the given class is - * null, the file name must be absolute. - *

- * Note that the file is not accessed until its getImageDate - * method is called. - *

- * - * @param clazz - * class for resource directory, or null - * @param filename - * the name of the file - */ - FileImageDescriptor(Class clazz, String filename) { - super(true); - this.location = clazz; - this.name = filename; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof FileImageDescriptor)) { - return false; - } - FileImageDescriptor other = (FileImageDescriptor) o; - return Objects.equals(location, other.location) && Objects.equals(name, other.name); - } - - /** - * {@inheritDoc} - *

- * The FileImageDescriptor implementation of this method is not used by - * {@link ImageDescriptor#createImage(boolean, Device)} as of version - * 3.4 so that the SWT OS optimized loading can be used. - */ - @Override - public ImageData getImageData(int zoom) { - return getImageData(name, zoom); - } - - /** - * Returns a stream on the image contents. Returns null if a stream could - * not be opened. - * - * @param zoom the zoom factor - * @return the buffered stream on the file or null if the - * file cannot be found - */ - @SuppressWarnings("resource") - private ImageData getImageData(String name, int zoom) { - if (zoom == 100 || URLImageDescriptor.canLoadAtZoom(() -> getStream(name), zoom)) { - return loadImageData(getStream(name), 100, zoom); - } - - InputStream xstream = getStream(getxName(name, zoom)); - if (xstream != null) { - return loadImageData(xstream, zoom, zoom); - } - - InputStream xpath = getStream(getxPath(name, zoom)); - if (xpath != null) { - return loadImageData(xpath, zoom, zoom); - } - - return null; - } - - /** - * try to obtain a stream for a given name, if the name does not match a valid - * resource null is returned - * - * @param fileName the filename to check - * @return an {@link InputStream} to read from, or null if fileName - * does not denotes an existing resource - */ - private InputStream getStream(String fileName) { - if (fileName != null) { - if (location != null) { - return location.getResourceAsStream(fileName); - } - try { - return new FileInputStream(fileName); - } catch (FileNotFoundException e) { - return null; - } - } - return null; - } - - static String getxPath(String name, int zoom) { - Matcher matcher = XPATH_PATTERN.matcher(name); - if (matcher.find()) { - try { - int currentWidth = Integer.parseInt(matcher.group(1)); - int desiredWidth = Math.round((zoom / 100f) * currentWidth); - int currentHeight = Integer.parseInt(matcher.group(2)); - int desiredHeight = Math.round((zoom / 100f) * currentHeight); - String lead = name.substring(0, matcher.start(1)); - String tail = name.substring(matcher.end(2)); - return lead + desiredWidth + "x" + desiredHeight + tail; //$NON-NLS-1$ - } catch (RuntimeException e) { - // should never happen but if then we can't use the alternative name... - } - } - return null; - } - - static String getxName(String name, int zoom) { - int dot = name.lastIndexOf('.'); - if (dot != -1 && (zoom == 150 || zoom == 200)) { - String lead = name.substring(0, dot); - String tail = name.substring(dot); - if (InternalPolicy.DEBUG_LOAD_URL_IMAGE_DESCRIPTOR_2x_PNG_FOR_GIF && ".gif".equalsIgnoreCase(tail)) { //$NON-NLS-1$ - tail = ".png"; //$NON-NLS-1$ - } - String x = zoom == 150 ? "@1.5x" : "@2x"; //$NON-NLS-1$ //$NON-NLS-2$ - return lead + x + tail; - } - return null; - } - - @Override - public int hashCode() { - int code = name.hashCode(); - if (location != null) { - code += location.hashCode(); - } - return code; - } - - /** - * The FileImageDescriptor implementation of this - * Object method returns a string representation of this - * object which is suitable only for debugging. - */ - @Override - public String toString() { - return "FileImageDescriptor(location=" + location + ", name=" + name + ")";//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ - } - - @Override - public Image createImage(boolean returnMissingImageOnError, Device device) { - if (InternalPolicy.DEBUG_LOAD_URL_IMAGE_DESCRIPTOR_2x) { - try { - // We really want a fresh ImageFileNameProvider instance to make - // sure the code that uses created images can use equals(), - // see Image#equals - return new Image(device, new ImageProvider()); - } catch (SWTException | IllegalArgumentException exception) { - // If we fail, fall back to the old 1x implementation. - } - } - - String path = getFilePath(name, true); - if (path == null) - return createDefaultImage(returnMissingImageOnError, device); - try { - return new Image(device, path); - } catch (SWTException exception) { - //if we fail try the default way using a stream - } - return super.createImage(returnMissingImageOnError, device); - } - - /** - * Return default image if returnMissingImageOnError is true. - * - * @return Image or null - */ - private Image createDefaultImage(boolean returnMissingImageOnError, - Device device) { - try { - if (returnMissingImageOnError) - return new Image(device, DEFAULT_IMAGE_DATA); - } catch (SWTException nextException) { - return null; - } - return null; - } - - /** - * Returns the filename for the ImageData. - * - * @param name the file name - * @return {@link String} or null if the file cannot be found - */ - String getFilePath(String name, boolean logIOException) { - if (location == null) - return IPath.fromOSString(name).toOSString(); - - URL resource = location.getResource(name); - - if (resource == null) - return null; - try { - if (!InternalPolicy.OSGI_AVAILABLE) {// Stand-alone case - return IPath.fromOSString(resource.getFile()).toOSString(); - } - return IPath.fromOSString(FileLocator.toFileURL(resource).getPath()).toOSString(); - } catch (IOException e) { - if (logIOException) { - Policy.logException(e); - } else if (InternalPolicy.DEBUG_LOG_URL_IMAGE_DESCRIPTOR_MISSING_2x) { - if (name.endsWith("@2x.png") || name.endsWith("@1.5x.png")) { //$NON-NLS-1$ //$NON-NLS-2$ - String message = "High-resolution image missing: " + location + ' ' + name; //$NON-NLS-1$ - Policy.getLog().log(Status.warning(message, e)); - } - } - return null; - } - } - - @Override - public T getAdapter(Class adapter) { - if (adapter == URL.class) { - if (location != null && name != null) { - return adapter.cast(location.getResource(name)); - } - } - if (adapter == ImageFileNameProvider.class) { - return adapter.cast(new ImageProvider()); - } - return null; - } - -} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java index e9714d53a8f..10a1244bbd0 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java @@ -16,8 +16,10 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.nio.file.Path; import java.util.function.Supplier; +import org.eclipse.jface.util.Policy; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Image; @@ -76,6 +78,9 @@ protected ImageDescriptor() { ImageDescriptor(boolean shouldBeCached) { super(shouldBeCached); } + + private static final ImageDescriptor NULL_IMAGE = createFromImageDataProvider(z -> null); + /** * Creates and returns a new image descriptor from a file. * @@ -84,7 +89,21 @@ protected ImageDescriptor() { * @return a new image descriptor */ public static ImageDescriptor createFromFile(Class location, String filename) { - return new FileImageDescriptor(location, filename); + URL url; + if (location == null) { + try { + url = Path.of(filename).toUri().toURL(); + } catch (MalformedURLException e) { + Policy.logException(e); + url = null; + } + } else { + url = location.getResource(filename); + } + if (url == null) { + return NULL_IMAGE; // Defer failure to the time when the image is created + } + return new URLImageDescriptor(url); } /** diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java index 9dc50ab74bb..5b0368be105 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java @@ -26,7 +26,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Function; -import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IAdaptable; @@ -104,7 +105,7 @@ public boolean equals(Object o) { public ImageData getImageData(int zoom) { URL tempURL = getURL(url); if (tempURL != null) { - if (zoom == 100 || canLoadAtZoom(() -> getStream(tempURL), zoom)) { + if (zoom == 100 || canLoadAtZoom(tempURL, zoom)) { return getImageData(tempURL, 100, zoom); } return getZoomedImageSource(tempURL, url, zoom, u -> getImageData(u, zoom, zoom)); @@ -120,7 +121,7 @@ private static R getZoomedImageSource(URL url, String urlString, int zoom, F return xdata; } } - String xpath = FileImageDescriptor.getxPath(urlString, zoom); + String xpath = getxPath(urlString, zoom); if (xpath != null) { URL xPathUrl = getURL(xpath); if (xPathUrl != null) { @@ -130,13 +131,8 @@ private static R getZoomedImageSource(URL url, String urlString, int zoom, F return null; } - @SuppressWarnings("resource") private static ImageData getImageData(URL url, int fileZoom, int targetZoom) { - return loadImageData(getStream(url), fileZoom, targetZoom); - } - - static ImageData loadImageData(InputStream stream, int fileZoom, int targetZoom) { - try (InputStream in = stream) { + try (InputStream in = getStream(url)) { if (in != null) { return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom); } @@ -158,8 +154,8 @@ private static ImageData loadImageFromStream(InputStream stream, int fileZoom, i } @SuppressWarnings("restriction") - static boolean canLoadAtZoom(Supplier stream, int zoom) { - try (InputStream in = stream.get()) { + private static boolean canLoadAtZoom(URL url, int zoom) { + try (InputStream in = getStream(url)) { if (in != null) { return FileFormat.canLoadAtZoom(new ElementAtZoom<>(in, 100), zoom); } @@ -225,6 +221,26 @@ private static URL getxURL(URL url, int zoom) { } + private static final Pattern XPATH_PATTERN = Pattern.compile("(\\d+)x(\\d+)"); //$NON-NLS-1$ + + private static String getxPath(String name, int zoom) { + Matcher matcher = XPATH_PATTERN.matcher(name); + if (matcher.find()) { + try { + int currentWidth = Integer.parseInt(matcher.group(1)); + int desiredWidth = Math.round((zoom / 100f) * currentWidth); + int currentHeight = Integer.parseInt(matcher.group(2)); + int desiredHeight = Math.round((zoom / 100f) * currentHeight); + String lead = name.substring(0, matcher.start(1)); + String tail = name.substring(matcher.end(2)); + return lead + desiredWidth + "x" + desiredHeight + tail; //$NON-NLS-1$ + } catch (RuntimeException e) { + // should never happen but if then we can't use the alternative name... + } + } + return null; + } + /** * Returns the filename for the ImageData. *