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 ec2bd5c33b5..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 @@ -25,7 +25,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.function.Supplier; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IAdaptable; @@ -52,55 +54,24 @@ */ class URLImageDescriptor extends ImageDescriptor implements IAdaptable { - private static class URLImageFileNameProvider implements ImageFileNameProvider { - - private final String url; - - public URLImageFileNameProvider(String url) { - this.url = url; - } - - @Override - public String getImagePath(int zoom) { + private ImageFileNameProvider createURLImageFileNameProvider() { + return zoom -> { URL tempURL = getURL(url); if (tempURL != null) { final boolean logIOException = zoom == 100; if (zoom == 100) { + // Do not take this path if the image file can be scaled up dynamically. + // The calling image will do that itself! return getFilePath(tempURL, logIOException); } - URL xUrl = getxURL(tempURL, zoom); - if (xUrl != null) { - String xResult = getFilePath(xUrl, logIOException); - if (xResult != null) { - return xResult; - } - } - String xpath = FileImageDescriptor.getxPath(url, zoom); - if (xpath != null) { - URL xPathUrl = getURL(xpath); - if (xPathUrl != null) { - return getFilePath(xPathUrl, logIOException); - } - } + return getZoomedImageSource(tempURL, url, zoom, u -> getFilePath(u, logIOException)); } return null; - } - + }; } - private static class URLImageDataProvider implements ImageDataProvider { - - private final String url; - - public URLImageDataProvider(String url) { - this.url = url; - } - - @Override - public ImageData getImageData(int zoom) { - return URLImageDescriptor.getImageData(url, zoom); - } - + private ImageDataProvider createURLImageDataProvider() { + return zoom -> getImageData(zoom); } private static long cumulativeTime; @@ -124,49 +95,44 @@ public ImageData getImageData(int zoom) { @Override public boolean equals(Object o) { - if (!(o instanceof URLImageDescriptor)) { + if (!(o instanceof URLImageDescriptor other)) { return false; } - return ((URLImageDescriptor) o).url.equals(this.url); + return other.url.equals(this.url); } @Override public ImageData getImageData(int zoom) { - return getImageData(url, zoom); - } - - private static ImageData getImageData(String url, 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); } - URL xUrl = getxURL(tempURL, zoom); - if (xUrl != null) { - ImageData xdata = getImageData(xUrl, zoom, zoom); - if (xdata != null) { - return xdata; - } + return getZoomedImageSource(tempURL, url, zoom, u -> getImageData(u, zoom, zoom)); + } + return null; + } + + private static R getZoomedImageSource(URL url, String urlString, int zoom, Function getImage) { + URL xUrl = getxURL(url, zoom); + if (xUrl != null) { + R xdata = getImage.apply(xUrl); + if (xdata != null) { + return xdata; } - String xpath = FileImageDescriptor.getxPath(url, zoom); - if (xpath != null) { - URL xPathUrl = getURL(xpath); - if (xPathUrl != null) { - return getImageData(xPathUrl, zoom, zoom); - } + } + String xpath = getxPath(urlString, zoom); + if (xpath != null) { + URL xPathUrl = getURL(xpath); + if (xPathUrl != null) { + return getImage.apply(xPathUrl); } } 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) { - ImageData result = null; - try (InputStream in = stream) { + try (InputStream in = getStream(url)) { if (in != null) { return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom); } @@ -176,9 +142,9 @@ static ImageData loadImageData(InputStream stream, int fileZoom, int targetZoom) // fall through otherwise } } catch (IOException e) { - Policy.getLog().log(Status.error(e.getLocalizedMessage(), e)); + Policy.logException(e); } - return result; + return null; } @SuppressWarnings("restriction") @@ -188,38 +154,21 @@ 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); } } catch (IOException e) { - Policy.getLog().log(Status.error(e.getLocalizedMessage(), e)); + Policy.logException(e); } return false; } - /** - * Returns a stream on the image contents. Returns null if a stream could - * not be opened. - * - * @return the stream for loading the data - */ - protected InputStream getStream() { - return getStream(getURL(url)); - } - private static InputStream getStream(URL url) { - if (url == null) { - return null; - } - try { if (InternalPolicy.OSGI_AVAILABLE) { - URL platformURL = FileLocator.find(url); - if (platformURL != null) { - url = platformURL; - } + url = resolvePathVariables(url); } return url.openStream(); } catch (IOException e) { @@ -265,13 +214,33 @@ private static URL getxURL(URL url, int zoom) { } return new URL(url.getProtocol(), url.getHost(), url.getPort(), file); } catch (MalformedURLException e) { - Policy.getLog().log(Status.error(e.getLocalizedMessage(), e)); + Policy.logException(e); } } return null; } + 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. * @@ -284,11 +253,7 @@ private static String getFilePath(URL url, boolean logIOException) { return IPath.fromOSString(url.getFile()).toOSString(); return null; } - - URL platformURL = FileLocator.find(url); - if (platformURL != null) { - url = platformURL; - } + url = resolvePathVariables(url); URL locatedURL = FileLocator.toFileURL(url); if (FILE_PROTOCOL.equalsIgnoreCase(locatedURL.getProtocol())) { String filePath = IPath.fromOSString(locatedURL.getPath()).toOSString(); @@ -310,6 +275,14 @@ private static String getFilePath(URL url, boolean logIOException) { } } + private static URL resolvePathVariables(URL url) { + URL platformURL = FileLocator.find(url); // Resolve variables within URL's path + if (platformURL != null) { + url = platformURL; + } + return url; + } + @Override public Image createImage(boolean returnMissingImageOnError, Device device) { long start = 0; @@ -323,7 +296,7 @@ public Image createImage(boolean returnMissingImageOnError, Device device) { // 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 URLImageFileNameProvider(url)); + return new Image(device, createURLImageFileNameProvider()); } catch (SWTException | IllegalArgumentException exception) { // If we fail fall back to the slower input stream method. } @@ -334,7 +307,7 @@ public Image createImage(boolean returnMissingImageOnError, Device device) { // We really want a fresh ImageDataProvider instance to make // sure the code that uses created images can use equals(), // see Image#equals - image = new Image(device, new URLImageDataProvider(url)); + image = new Image(device, createURLImageDataProvider()); } catch (SWTException e) { if (e.code != SWT.ERROR_INVALID_IMAGE) { throw e; @@ -383,7 +356,7 @@ private static URL getURL(String urlString) { try { result = new URL(urlString); } catch (MalformedURLException e) { - Policy.getLog().log(Status.error(e.getLocalizedMessage(), e)); + Policy.logException(e); } return result; } @@ -394,10 +367,10 @@ public T getAdapter(Class adapter) { return adapter.cast(getURL(url)); } if (adapter == ImageFileNameProvider.class) { - return adapter.cast(new URLImageFileNameProvider(url)); + return adapter.cast(createURLImageFileNameProvider()); } if (adapter == ImageDataProvider.class) { - return adapter.cast(new URLImageDataProvider(url)); + return adapter.cast(createURLImageDataProvider()); } return null; }