001 /**
002 * <copyright>
003 *
004 * Copyright (c) 2002-2007 IBM Corporation and others.
005 * All rights reserved. This program and the accompanying materials
006 * are made available under the terms of the Eclipse Public License v1.0
007 * which accompanies this distribution, and is available at
008 * http://www.eclipse.org/legal/epl-v10.html
009 *
010 * Contributors:
011 * IBM - Initial API and implementation
012 *
013 * </copyright>
014 *
015 * $Id: DelegatingResourceLocator.java,v 1.3 2008/04/22 13:35:46 emerks Exp $
016 */
017 package org.eclipse.emf.common.util;
018
019
020 import java.io.IOException;
021 import java.io.InputStream;
022 import java.net.MalformedURLException;
023 import java.net.URL;
024 import java.text.MessageFormat;
025 import java.util.HashMap;
026 import java.util.Map;
027 import java.util.MissingResourceException;
028 import java.util.PropertyResourceBundle;
029 import java.util.ResourceBundle;
030
031 import org.eclipse.emf.common.CommonPlugin;
032 import org.eclipse.emf.common.util.ResourceLocator;
033 import org.eclipse.emf.common.util.WrappedException;
034
035
036 /**
037 * An abstract resource locator implementation
038 * comprising a {@link #getPrimaryResourceLocator() primary locator}
039 * and a series {@link #getDelegateResourceLocators() delegate locators}.
040 */
041 public abstract class DelegatingResourceLocator implements ResourceLocator
042 {
043 /**
044 * The cached base URL.
045 */
046 protected URL baseURL;
047
048 /**
049 * The resource bundle containing untranslated strings.
050 */
051 protected ResourceBundle untranslatedResourceBundle;
052
053 /**
054 * The resource bundle containing translated strings.
055 */
056 protected ResourceBundle resourceBundle;
057
058 /**
059 * A cache of the translated strings.
060 */
061 protected Map<String, String> strings = new HashMap<String, String>();
062
063 /**
064 * A cache of the untranslated strings.
065 */
066 protected Map<String, String> untranslatedStrings = new HashMap<String, String>();
067
068 /**
069 * A cache of the image descriptions.
070 */
071 protected Map<String, Object> images = new HashMap<String, Object>();
072
073 /**
074 * Whether to translate strings by default.
075 */
076 protected boolean shouldTranslate = true;
077
078 /**
079 * Creates an instance.
080 */
081 public DelegatingResourceLocator()
082 {
083 super();
084 }
085
086 /**
087 * Returns the primary resource locator.
088 * @return the primary resource locator.
089 */
090 protected abstract ResourceLocator getPrimaryResourceLocator();
091
092 /**
093 * Returns the delegate resource locators.
094 * @return the delegate resource locators.
095 */
096 protected abstract ResourceLocator [] getDelegateResourceLocators();
097
098 private static final URI DOT = URI.createURI(".");
099
100 /*
101 * Javadoc copied from interface.
102 */
103 public URL getBaseURL()
104 {
105 if (baseURL == null)
106 {
107 if (getPrimaryResourceLocator() == null)
108 {
109 try
110 {
111 // Determine the base URL by looking for the plugin.properties file in the standard way.
112 //
113 Class<? extends DelegatingResourceLocator> theClass = getClass();
114 URL pluginPropertiesURL = theClass.getResource("plugin.properties");
115 if (pluginPropertiesURL == null)
116 {
117 // If that fails, determine the URL for the class itself.
118 // The URL will be of one of the following forms,
119 // so there are a few good places to consider looking for the plugin.properties.
120 //
121 // For a plugin.xml with runtime="common.jar":
122 // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/common.jar!/org/eclipse/common/CommonPlugin.class
123 //
124 // For a plugin.xml with runtime="runtime/common.jar":
125 // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/runtime/common.jar!/org/eclipse/common/CommonPlugin.class
126 //
127 // For a plugin.xml with runtime="." where the plugin is jarred:
128 // jar:file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common.jar!/org/eclipse/common/CommonPlugin.class
129 //
130 // For a plugin.xml with runtime="." where the plugin is not jarred.
131 // file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/org/eclipse/emf/common/CommonPlugin.class
132 //
133 // Running in PDE with bin on classpath:
134 // file:/D:/sandbox/unpackage1-3.1M7/eclipse/plugins/org.eclipse.emf.common/bin/org/eclipse/emf/common/CommonPlugin.class
135 //
136 String className = theClass.getName();
137 int index = className.lastIndexOf(".");
138 URL classURL = theClass.getResource((index == -1 ? className : className.substring(index + 1)) + ".class");
139 URI uri = URI.createURI(classURL.toString());
140
141 // Trim off the segments corresponding to the package nesting.
142 //
143 int count = 1;
144 for (int i = 0; (i = className.indexOf('.', i)) != -1; ++i)
145 {
146 ++count;
147 }
148 uri = uri.trimSegments(count);
149
150 // For an archive URI, check for the plugin.properties in the archive.
151 //
152 if (URI.isArchiveScheme(uri.scheme()))
153 {
154 try
155 {
156 // If we can open an input stream, then the plugin.properties is there, and we have a good base URL.
157 //
158 InputStream inputStream = new URL(uri.appendSegment("plugin.properties").toString()).openStream();
159 inputStream.close();
160 baseURL = new URL(uri.toString());
161 }
162 catch (IOException exception)
163 {
164 // If the plugin.properties isn't within the root of the archive,
165 // create a new URI for the folder location of the archive,
166 // so we can look in the folder that contains it.
167 //
168 uri = URI.createURI(uri.authority()).trimSegments(1);
169 }
170 }
171
172 // If we didn't find the plugin.properties in the usual place nor in the archive...
173 //
174 if (baseURL == null)
175 {
176 // Trim off the "bin" or "runtime" segment.
177 //
178 String lastSegment = uri.lastSegment();
179 if ("bin".equals(lastSegment) || "runtime".equals(lastSegment))
180 {
181 uri = uri.trimSegments(1);
182 }
183 uri = uri.appendSegment("plugin.properties");
184 try
185 {
186 // If we can open an input stream, then the plugin.properties is in the folder, and we have a good base URL.
187 //
188 InputStream inputStream = new URL(uri.toString()).openStream();
189 inputStream.close();
190 baseURL = new URL(DOT.resolve(uri).toString());
191 }
192 catch (IOException exception)
193 {
194 // Continue with the established base URL.
195 }
196 }
197
198 // If we still don't have a good base URL, complain about it.
199 //
200 if (baseURL == null)
201 {
202 String resourceName =
203 index == -1 ?
204 "plugin.properties" :
205 className.substring(0, index + 1).replace('.','/') + "plugin.properties";
206 throw new MissingResourceException("Missing properties: " + resourceName, theClass.getName(), "plugin.properties");
207 }
208 }
209 else
210 {
211 baseURL = new URL(DOT.resolve(URI.createURI(pluginPropertiesURL.toString())).toString());
212 }
213 }
214 catch (IOException exception)
215 {
216 throw new WrappedException(exception);
217 }
218 }
219 else
220 {
221 baseURL = getPrimaryResourceLocator().getBaseURL();
222 }
223 }
224
225 return baseURL;
226 }
227
228 /*
229 * Javadoc copied from interface.
230 */
231 public Object getImage(String key)
232 {
233 Object result = images.get(key);
234 if (result == null)
235 {
236 ResourceLocator pluginResourceLocator = getPrimaryResourceLocator();
237 if (pluginResourceLocator == null)
238 {
239 try
240 {
241 result = doGetImage(key);
242 }
243 catch (MalformedURLException exception)
244 {
245 throw new WrappedException(exception);
246 }
247 catch (IOException exception)
248 {
249 result = delegatedGetImage(key);
250 }
251 }
252 else
253 {
254 try
255 {
256 result = pluginResourceLocator.getImage(key);
257 }
258 catch (MissingResourceException exception)
259 {
260 result = delegatedGetImage(key);
261 }
262 }
263
264 images.put(key, result);
265 }
266
267 return result;
268 }
269
270 /**
271 * Does the work of fetching the image associated with the key.
272 * It ensures that the image exists.
273 * @param key the key of the image to fetch.
274 * @exception IOException if an image doesn't exist.
275 * @return the description of the image associated with the key.
276 */
277 protected Object doGetImage(String key) throws IOException
278 {
279 URL url = new URL(getBaseURL() + "icons/" + key + extensionFor(key));
280 InputStream inputStream = url.openStream();
281 inputStream.close();
282 return url;
283 }
284
285 /**
286 * Computes the file extension to be used with the key to specify an image resource.
287 * @param key the key for the imagine.
288 * @return the file extension to be used with the key to specify an image resource.
289 */
290 protected static String extensionFor(String key)
291 {
292 String result = ".gif";
293 int index = key.lastIndexOf('.');
294 if (index != -1)
295 {
296 String extension = key.substring(index + 1);
297 if ("png".equalsIgnoreCase(extension) ||
298 "gif".equalsIgnoreCase(extension) ||
299 "bmp".equalsIgnoreCase(extension) ||
300 "ico".equalsIgnoreCase(extension) ||
301 "jpg".equalsIgnoreCase(extension) ||
302 "jpeg".equalsIgnoreCase(extension) ||
303 "tif".equalsIgnoreCase(extension) ||
304 "tiff".equalsIgnoreCase(extension))
305 {
306 result = "";
307 }
308 }
309 return result;
310 }
311
312 /**
313 * Does the work of fetching the image associated with the key,
314 * when the image resource is not available locally.
315 * @param key the key of the image to fetch.
316 * @exception MissingResourceException if the image resource doesn't exist anywhere.
317 * @see #getDelegateResourceLocators()
318 */
319 protected Object delegatedGetImage(String key) throws MissingResourceException
320 {
321 ResourceLocator[] delegateResourceLocators = getDelegateResourceLocators();
322 for (int i = 0; i < delegateResourceLocators.length; ++i)
323 {
324 try
325 {
326 return delegateResourceLocators[i].getImage(key);
327 }
328 catch (MissingResourceException exception)
329 {
330 // Ignore the exception since we will throw one when all else fails.
331 }
332 }
333
334 throw
335 new MissingResourceException
336 (CommonPlugin.INSTANCE.getString("_UI_ImageResourceNotFound_exception", new Object [] { key }),
337 getClass().getName(),
338 key);
339 }
340
341 /**
342 * Indicates whether strings should be translated by default.
343 *
344 * @return <code>true</code> if strings should be translated by default; <code>false</code> otherwise.
345 */
346 public boolean shouldTranslate()
347 {
348 return shouldTranslate;
349 }
350
351 /**
352 * Sets whether strings should be translated by default.
353 *
354 * @param shouldTranslate whether strings should be translated by default.
355 */
356 public void setShouldTranslate(boolean shouldTranslate)
357 {
358 this.shouldTranslate = shouldTranslate;
359 }
360
361 /*
362 * Javadoc copied from interface.
363 */
364 public String getString(String key)
365 {
366 return getString(key, shouldTranslate());
367 }
368
369 /*
370 * Javadoc copied from interface.
371 */
372 public String getString(String key, boolean translate)
373 {
374 Map<String, String> stringMap = translate ? strings : untranslatedStrings;
375 String result = stringMap.get(key);
376 if (result == null)
377 {
378 try
379 {
380 ResourceLocator pluginResourceLocator = getPrimaryResourceLocator();
381 if (pluginResourceLocator == null)
382 {
383 result = doGetString(key, translate);
384 }
385 else
386 {
387 result = pluginResourceLocator.getString(key, translate);
388 }
389 }
390 catch (MissingResourceException exception)
391 {
392 result = delegatedGetString(key, translate);
393 }
394
395 stringMap.put(key, result);
396 }
397
398 return result;
399 }
400
401 /**
402 * Does the work of fetching the string associated with the key.
403 * It ensures that the string exists.
404 * @param key the key of the string to fetch.
405 * @exception MissingResourceException if a string doesn't exist.
406 * @return the string associated with the key.
407 */
408 protected String doGetString(String key, boolean translate) throws MissingResourceException
409 {
410 ResourceBundle bundle = translate ? resourceBundle : untranslatedResourceBundle;
411 if (bundle == null)
412 {
413 String packageName = getClass().getName();
414 int index = packageName.lastIndexOf(".");
415 if (index != -1)
416 {
417 packageName = packageName.substring(0, index);
418 }
419 if (translate)
420 {
421 try
422 {
423 bundle = resourceBundle = ResourceBundle.getBundle(packageName + ".plugin");
424 }
425 catch (MissingResourceException exception)
426 {
427 // If the bundle can't be found the normal way, try to find it as the base URL.
428 // If that also doesn't work, rethrow the original exception.
429 //
430 try
431 {
432 InputStream inputStream = new URL(getBaseURL().toString() + "plugin.properties").openStream();
433 bundle = untranslatedResourceBundle = resourceBundle = new PropertyResourceBundle(inputStream);
434 inputStream.close();
435 }
436 catch (IOException ioException)
437 {
438 // We'll rethrow the original exception, not this one.
439 }
440 if (bundle == null)
441 {
442 throw exception;
443 }
444 }
445 }
446 else
447 {
448 String resourceName = getBaseURL().toString() + "plugin.properties";
449 try
450 {
451 InputStream inputStream = new URL(resourceName).openStream();
452 bundle = untranslatedResourceBundle = new PropertyResourceBundle(inputStream);
453 inputStream.close();
454 }
455 catch (IOException ioException)
456 {
457 throw new MissingResourceException("Missing properties: " + resourceName, getClass().getName(), "plugin.properties");
458 }
459 }
460 }
461 return bundle.getString(key);
462 }
463
464 /**
465 * Does the work of fetching the string associated with the key,
466 * when the string resource is not available locally.
467 * @param key the key of the string to fetch.
468 * @exception MissingResourceException if the string resource doesn't exist anywhere.
469 * @see #getDelegateResourceLocators()
470 */
471 protected String delegatedGetString(String key, boolean translate)
472 {
473 ResourceLocator[] delegateResourceLocators = getDelegateResourceLocators();
474 for (int i = 0; i < delegateResourceLocators.length; ++i)
475 {
476 try
477 {
478 return delegateResourceLocators[i].getString(key, translate);
479 }
480 catch (MissingResourceException exception)
481 {
482 // Ignore this since we will throw an exception when all else fails.
483 }
484 }
485
486 throw
487 new MissingResourceException
488 (MessageFormat.format("The string resource ''{0}'' could not be located", new Object [] { key }),
489 getClass().getName(),
490 key);
491 }
492
493 /*
494 * Javadoc copied from interface.
495 */
496 public String getString(String key, Object [] substitutions)
497 {
498 return getString(key, substitutions, shouldTranslate());
499 }
500
501 /*
502 * Javadoc copied from interface.
503 */
504 public String getString(String key, Object [] substitutions, boolean translate)
505 {
506 return MessageFormat.format(getString(key, translate), substitutions);
507 }
508 }