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    }