| /** |
| * Copyright (c) 2002-2007 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| */ |
| package org.eclipse.emf.common.util; |
| |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.text.MessageFormat; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.PropertyResourceBundle; |
| import java.util.ResourceBundle; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| |
| import org.eclipse.emf.common.CommonPlugin; |
| |
| |
| /** |
| * An abstract resource locator implementation |
| * comprising a {@link #getPrimaryResourceLocator() primary locator} |
| * and a series {@link #getDelegateResourceLocators() delegate locators}. |
| */ |
| public abstract class DelegatingResourceLocator implements ResourceLocator |
| { |
| /** |
| * The cached base URL. |
| */ |
| protected URL baseURL; |
| |
| /** |
| * The resource bundle containing untranslated strings. |
| */ |
| protected ResourceBundle untranslatedResourceBundle; |
| |
| /** |
| * The resource bundle containing translated strings. |
| */ |
| protected ResourceBundle resourceBundle; |
| |
| /** |
| * A cache of the translated strings. |
| */ |
| protected Map<String, String> strings = new HashMap<String, String>(); |
| |
| /** |
| * A cache of the untranslated strings. |
| */ |
| protected Map<String, String> untranslatedStrings = new HashMap<String, String>(); |
| |
| /** |
| * A cache of the image descriptions. |
| */ |
| protected Map<String, Object> images = new HashMap<String, Object>(); |
| |
| /** |
| * Whether to translate strings by default. |
| */ |
| protected boolean shouldTranslate = true; |
| |
| /** |
| * The default localization properties file. |
| * @since 2.14 |
| */ |
| protected String bundleLocalization = "plugin.properties"; |
| |
| /** |
| * Creates an instance. |
| */ |
| public DelegatingResourceLocator() |
| { |
| super(); |
| } |
| |
| /** |
| * Returns the primary resource locator. |
| * @return the primary resource locator. |
| */ |
| protected abstract ResourceLocator getPrimaryResourceLocator(); |
| |
| /** |
| * Returns the delegate resource locators. |
| * @return the delegate resource locators. |
| */ |
| protected abstract ResourceLocator [] getDelegateResourceLocators(); |
| |
| private static final URI DOT = URI.createURI("."); |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public URL getBaseURL() |
| { |
| if (baseURL == null) |
| { |
| if (getPrimaryResourceLocator() == null) |
| { |
| try |
| { |
| // Determine the base URL by looking for the plugin.properties file in the standard way. |
| // |
| Class<? extends DelegatingResourceLocator> theClass = getClass(); |
| URL pluginPropertiesURL = theClass.getResource("plugin.properties"); |
| if (pluginPropertiesURL == null) |
| { |
| // If that fails, determine the URL for the class itself. |
| // The URL will be of one of the following forms, |
| // so there are a few good places to consider looking for the plugin.properties. |
| // |
| // For a plugin.xml with runtime="common.jar": |
| // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/common.jar!/org/eclipse/common/CommonPlugin.class |
| // |
| // For a plugin.xml with runtime="runtime/common.jar": |
| // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/runtime/common.jar!/org/eclipse/common/CommonPlugin.class |
| // |
| // For a plugin.xml with runtime="." where the plugin is jarred: |
| // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common.jar!/org/eclipse/common/CommonPlugin.class |
| // |
| // For a plugin.xml with runtime="." where the plugin is not jarred. |
| // file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/org/eclipse/emf/common/CommonPlugin.class |
| // |
| // Running in PDE with bin on classpath: |
| // file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/bin/org/eclipse/emf/common/CommonPlugin.class |
| // |
| String className = theClass.getName(); |
| int index = className.lastIndexOf("."); |
| URL classURL = theClass.getResource((index == -1 ? className : className.substring(index + 1)) + ".class"); |
| URI uri = URI.createURI(classURL.toString()); |
| |
| // Trim off the segments corresponding to the package nesting. |
| // |
| int count = 1; |
| for (int i = 0; (i = className.indexOf('.', i)) != -1; ++i) |
| { |
| ++count; |
| } |
| uri = uri.trimSegments(count); |
| |
| // For an archive URI, check for the plugin.properties in the archive. |
| // |
| if (URI.isArchiveScheme(uri.scheme())) |
| { |
| try |
| { |
| String bundleLocalization = getBundleLocalization(new URL(uri.appendSegment("META-INF").appendSegment("MANIFEST.MF").toString())); |
| if (bundleLocalization != null) |
| { |
| this.bundleLocalization = bundleLocalization; |
| } |
| baseURL = new URL(uri.toString()); |
| } |
| catch (IOException exception) |
| { |
| try |
| { |
| // If we can open an input stream, then the plugin.properties is there, and we have a good base URL. |
| // |
| InputStream inputStream = new URL(uri.appendSegment("plugin.properties").toString()).openStream(); |
| inputStream.close(); |
| baseURL = new URL(uri.toString()); |
| } |
| catch (IOException nestedException) |
| { |
| // If the plugin.properties isn't within the root of the archive, |
| // create a new URI for the folder location of the archive, |
| // so we can look in the folder that contains it. |
| // |
| uri = URI.createURI(uri.authority()).trimSegments(1); |
| } |
| } |
| } |
| |
| // If we didn't find the plugin.properties in the usual place nor in the archive... |
| // |
| if (baseURL == null) |
| { |
| // Trim off the "bin" or "runtime" segment. |
| // |
| String lastSegment = uri.lastSegment(); |
| if ("bin".equals(lastSegment) || "runtime".equals(lastSegment)) |
| { |
| uri = uri.trimSegments(1); |
| } |
| |
| try |
| { |
| String bundleLocalization = getBundleLocalization(new URL(uri.appendSegment("META-INF").appendSegment("MANIFEST.MF").toString())); |
| if (bundleLocalization != null) |
| { |
| this.bundleLocalization = bundleLocalization; |
| } |
| String baseURI = uri.toString(); |
| baseURL = new URL(baseURI.endsWith("/") ? baseURI : baseURI + "/"); |
| } |
| catch (IOException exception) |
| { |
| uri = uri.appendSegment("plugin.properties"); |
| try |
| { |
| // If we can open an input stream, then the plugin.properties is in the folder, and we have a good base URL. |
| // |
| InputStream inputStream = new URL(uri.toString()).openStream(); |
| inputStream.close(); |
| baseURL = new URL(DOT.resolve(uri).toString()); |
| } |
| catch (IOException nextedException) |
| { |
| // We're going to fail. |
| } |
| } |
| } |
| |
| // If we still don't have a good base URL, complain about it. |
| // |
| if (baseURL == null) |
| { |
| String resourceName = |
| index == -1 ? |
| "plugin.properties" : |
| className.substring(0, index + 1).replace('.','/') + "plugin.properties"; |
| throw new MissingResourceException("Missing properties: " + resourceName, theClass.getName(), "plugin.properties"); |
| } |
| } |
| else |
| { |
| baseURL = new URL(DOT.resolve(URI.createURI(pluginPropertiesURL.toString())).toString()); |
| } |
| } |
| catch (IOException exception) |
| { |
| throw new WrappedException(exception); |
| } |
| } |
| else |
| { |
| baseURL = getPrimaryResourceLocator().getBaseURL(); |
| } |
| } |
| |
| return baseURL; |
| } |
| |
| /** |
| * @since 2.14 |
| */ |
| protected String getBundleLocalization(URL manifestURL) throws IOException |
| { |
| InputStream inputStream = null; |
| try |
| { |
| inputStream = manifestURL.openStream(); |
| Manifest manifest = new Manifest(inputStream); |
| Attributes mainAttributes = manifest.getMainAttributes(); |
| String value = mainAttributes.getValue("Bundle-Localization"); |
| if (value != null) |
| { |
| return value + ".properties"; |
| } |
| } |
| finally |
| { |
| if (inputStream != null) |
| { |
| inputStream.close(); |
| } |
| } |
| |
| return null; |
| } |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public Object getImage(String key) |
| { |
| Object result = images.get(key); |
| if (result == null) |
| { |
| ResourceLocator pluginResourceLocator = getPrimaryResourceLocator(); |
| if (pluginResourceLocator == null) |
| { |
| try |
| { |
| result = doGetImage(key); |
| } |
| catch (MalformedURLException exception) |
| { |
| throw new WrappedException(exception); |
| } |
| catch (IOException exception) |
| { |
| result = delegatedGetImage(key); |
| } |
| } |
| else |
| { |
| try |
| { |
| result = pluginResourceLocator.getImage(key); |
| } |
| catch (MissingResourceException exception) |
| { |
| result = delegatedGetImage(key); |
| } |
| } |
| |
| images.put(key, result); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Does the work of fetching the image associated with the key. |
| * It ensures that the image exists. |
| * @param key the key of the image to fetch. |
| * @exception IOException if an image doesn't exist. |
| * @return the description of the image associated with the key. |
| */ |
| protected Object doGetImage(String key) throws IOException |
| { |
| URL url = new URL(getBaseURL() + "icons/" + key + extensionFor(key)); |
| InputStream inputStream = url.openStream(); |
| inputStream.close(); |
| return url; |
| } |
| |
| /** |
| * Computes the file extension to be used with the key to specify an image resource. |
| * @param key the key for the imagine. |
| * @return the file extension to be used with the key to specify an image resource. |
| */ |
| protected static String extensionFor(String key) |
| { String result = ".gif"; |
| int index = key.lastIndexOf('.'); |
| if (index != -1) |
| { |
| String extension = key.substring(index + 1); |
| if ("png".equalsIgnoreCase(extension) || |
| "gif".equalsIgnoreCase(extension) || |
| "bmp".equalsIgnoreCase(extension) || |
| "ico".equalsIgnoreCase(extension) || |
| "jpg".equalsIgnoreCase(extension) || |
| "jpeg".equalsIgnoreCase(extension) || |
| "tif".equalsIgnoreCase(extension) || |
| "tiff".equalsIgnoreCase(extension)) |
| { |
| result = ""; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Does the work of fetching the image associated with the key, |
| * when the image resource is not available locally. |
| * @param key the key of the image to fetch. |
| * @exception MissingResourceException if the image resource doesn't exist anywhere. |
| * @see #getDelegateResourceLocators() |
| */ |
| protected Object delegatedGetImage(String key) throws MissingResourceException |
| { |
| ResourceLocator[] delegateResourceLocators = getDelegateResourceLocators(); |
| for (int i = 0; i < delegateResourceLocators.length; ++i) |
| { |
| try |
| { |
| return delegateResourceLocators[i].getImage(key); |
| } |
| catch (MissingResourceException exception) |
| { |
| // Ignore the exception since we will throw one when all else fails. |
| } |
| } |
| |
| throw |
| new MissingResourceException |
| (CommonPlugin.INSTANCE.getString("_UI_ImageResourceNotFound_exception", new Object [] { key }), |
| getClass().getName(), |
| key); |
| } |
| |
| /** |
| * Indicates whether strings should be translated by default. |
| * |
| * @return <code>true</code> if strings should be translated by default; <code>false</code> otherwise. |
| */ |
| public boolean shouldTranslate() |
| { |
| return shouldTranslate; |
| } |
| |
| /** |
| * Sets whether strings should be translated by default. |
| * |
| * @param shouldTranslate whether strings should be translated by default. |
| */ |
| public void setShouldTranslate(boolean shouldTranslate) |
| { |
| this.shouldTranslate = shouldTranslate; |
| } |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public String getString(String key) |
| { |
| return getString(key, shouldTranslate()); |
| } |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public String getString(String key, boolean translate) |
| { |
| Map<String, String> stringMap = translate ? strings : untranslatedStrings; |
| String result = stringMap.get(key); |
| if (result == null) |
| { |
| try |
| { |
| ResourceLocator pluginResourceLocator = getPrimaryResourceLocator(); |
| if (pluginResourceLocator == null) |
| { |
| result = doGetString(key, translate); |
| } |
| else |
| { |
| result = pluginResourceLocator.getString(key, translate); |
| } |
| } |
| catch (MissingResourceException exception) |
| { |
| result = delegatedGetString(key, translate); |
| } |
| |
| stringMap.put(key, result); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Does the work of fetching the string associated with the key. |
| * It ensures that the string exists. |
| * @param key the key of the string to fetch. |
| * @exception MissingResourceException if a string doesn't exist. |
| * @return the string associated with the key. |
| */ |
| protected String doGetString(String key, boolean translate) throws MissingResourceException |
| { |
| ResourceBundle bundle = translate ? resourceBundle : untranslatedResourceBundle; |
| if (bundle == null) |
| { |
| String packageName = getClass().getName(); |
| int index = packageName.lastIndexOf("."); |
| if (index != -1) |
| { |
| packageName = packageName.substring(0, index); |
| } |
| URL baseURL = getBaseURL(); |
| if (translate) |
| { |
| try |
| { |
| bundle = resourceBundle = ResourceBundle.getBundle(packageName + ".plugin"); |
| } |
| catch (MissingResourceException exception) |
| { |
| // If the bundle can't be found the normal way, try to find it as the base URL. |
| // If that also doesn't work, rethrow the original exception. |
| // |
| try |
| { |
| InputStream inputStream = new URL(baseURL.toString() + bundleLocalization).openStream(); |
| bundle = untranslatedResourceBundle = resourceBundle = new PropertyResourceBundle(inputStream); |
| inputStream.close(); |
| } |
| catch (IOException ioException) |
| { |
| // We'll rethrow the original exception, not this one. |
| } |
| if (bundle == null) |
| { |
| throw exception; |
| } |
| } |
| } |
| else |
| { |
| String resourceName = baseURL.toString() + bundleLocalization; |
| try |
| { |
| InputStream inputStream = new URL(resourceName).openStream(); |
| bundle = untranslatedResourceBundle = new PropertyResourceBundle(inputStream); |
| inputStream.close(); |
| } |
| catch (IOException ioException) |
| { |
| throw new MissingResourceException("Missing properties: " + resourceName, getClass().getName(), "plugin.properties"); |
| } |
| } |
| } |
| return bundle.getString(key); |
| } |
| |
| /** |
| * Does the work of fetching the string associated with the key, |
| * when the string resource is not available locally. |
| * @param key the key of the string to fetch. |
| * @exception MissingResourceException if the string resource doesn't exist anywhere. |
| * @see #getDelegateResourceLocators() |
| */ |
| protected String delegatedGetString(String key, boolean translate) |
| { |
| ResourceLocator[] delegateResourceLocators = getDelegateResourceLocators(); |
| for (int i = 0; i < delegateResourceLocators.length; ++i) |
| { |
| try |
| { |
| return delegateResourceLocators[i].getString(key, translate); |
| } |
| catch (MissingResourceException exception) |
| { |
| // Ignore this since we will throw an exception when all else fails. |
| } |
| } |
| |
| throw |
| new MissingResourceException |
| (MessageFormat.format("The string resource ''{0}'' could not be located", new Object [] { key }), |
| getClass().getName(), |
| key); |
| } |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public String getString(String key, Object [] substitutions) |
| { |
| return getString(key, substitutions, shouldTranslate()); |
| } |
| |
| /* |
| * Javadoc copied from interface. |
| */ |
| public String getString(String key, Object [] substitutions, boolean translate) |
| { |
| return MessageFormat.format(getString(key, translate), substitutions); |
| } |
| } |