Clover coverage report - Code Coverage for tapestry release 4.0-rc-2
Coverage timestamp: Sat Dec 17 2005 09:39:46 PST
file stats: LOC: 361   Methods: 16
NCLOC: 225   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ComponentMessagesSourceImpl.java 96.4% 97.9% 93.8% 97.1%
coverage coverage
 1    // Copyright 2004, 2005 The Apache Software Foundation
 2    //
 3    // Licensed under the Apache License, Version 2.0 (the "License");
 4    // you may not use this file except in compliance with the License.
 5    // You may obtain a copy of the License at
 6    //
 7    // http://www.apache.org/licenses/LICENSE-2.0
 8    //
 9    // Unless required by applicable law or agreed to in writing, software
 10    // distributed under the License is distributed on an "AS IS" BASIS,
 11    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    // See the License for the specific language governing permissions and
 13    // limitations under the License.
 14   
 15    package org.apache.tapestry.services.impl;
 16   
 17    import java.io.BufferedInputStream;
 18    import java.io.IOException;
 19    import java.io.InputStream;
 20    import java.net.URL;
 21    import java.util.ArrayList;
 22    import java.util.Collections;
 23    import java.util.HashMap;
 24    import java.util.Iterator;
 25    import java.util.List;
 26    import java.util.Locale;
 27    import java.util.Map;
 28    import java.util.Properties;
 29   
 30    import org.apache.hivemind.ApplicationRuntimeException;
 31    import org.apache.hivemind.Messages;
 32    import org.apache.hivemind.Resource;
 33    import org.apache.hivemind.util.Defense;
 34    import org.apache.hivemind.util.LocalizedNameGenerator;
 35    import org.apache.tapestry.IComponent;
 36    import org.apache.tapestry.INamespace;
 37    import org.apache.tapestry.event.ResetEventListener;
 38    import org.apache.tapestry.services.ComponentMessagesSource;
 39    import org.apache.tapestry.services.ComponentPropertySource;
 40    import org.apache.tapestry.util.text.LocalizedProperties;
 41   
 42    /**
 43    * Service used to access localized properties for a component.
 44    *
 45    * @author Howard Lewis Ship
 46    * @since 2.0.4
 47    */
 48   
 49    public class ComponentMessagesSourceImpl implements ComponentMessagesSource, ResetEventListener
 50    {
 51    private Properties _emptyProperties = new Properties();
 52   
 53    private static final String SUFFIX = ".properties";
 54   
 55    /**
 56    * The name of the component/application/etc property that will be used to determine the
 57    * encoding to use when loading the messages
 58    */
 59   
 60    public static final String MESSAGES_ENCODING_PROPERTY_NAME = "org.apache.tapestry.messages-encoding";
 61   
 62    /**
 63    * Map of Maps. The outer map is keyed on component specification location (a{@link Resource}.
 64    * This inner map is keyed on locale and the value is a {@link Properties}.
 65    */
 66   
 67    private Map _componentCache = new HashMap();
 68   
 69    private ComponentPropertySource _componentPropertySource;
 70   
 71    /**
 72    * Returns an instance of {@link Properties}containing the properly localized messages for the
 73    * component, in the {@link Locale}identified by the component's containing page.
 74    */
 75   
 76  31 protected synchronized Properties getLocalizedProperties(IComponent component)
 77    {
 78  31 Defense.notNull(component, "component");
 79   
 80  31 Resource specificationLocation = component.getSpecification().getSpecificationLocation();
 81  31 Locale locale = component.getPage().getLocale();
 82   
 83  31 Map propertiesMap = findPropertiesMapForResource(specificationLocation);
 84   
 85  31 Properties result = (Properties) propertiesMap.get(locale);
 86   
 87  31 if (result == null)
 88    {
 89   
 90    // Not found, create it now.
 91   
 92  30 result = assembleComponentProperties(
 93    component,
 94    specificationLocation,
 95    propertiesMap,
 96    locale);
 97   
 98  30 propertiesMap.put(locale, result);
 99    }
 100   
 101  31 return result;
 102    }
 103   
 104  109 private Map findPropertiesMapForResource(Resource resource)
 105    {
 106  109 Map result = (Map) _componentCache.get(resource);
 107   
 108  109 if (result == null)
 109    {
 110  48 result = new HashMap();
 111  48 _componentCache.put(resource, result);
 112    }
 113   
 114  109 return result;
 115    }
 116   
 117  78 private Properties getNamespaceProperties(IComponent component, Locale locale)
 118    {
 119  78 INamespace namespace = component.getNamespace();
 120   
 121  78 Resource namespaceLocation = namespace.getSpecificationLocation();
 122   
 123  78 Map propertiesMap = findPropertiesMapForResource(namespaceLocation);
 124   
 125  78 Properties result = (Properties) propertiesMap.get(locale);
 126   
 127  78 if (result == null)
 128    {
 129  70 result = assembleNamespaceProperties(namespace, propertiesMap, locale);
 130   
 131  70 propertiesMap.put(locale, result);
 132    }
 133   
 134  78 return result;
 135    }
 136   
 137  30 private Properties assembleComponentProperties(IComponent component,
 138    Resource baseResourceLocation, Map propertiesMap, Locale locale)
 139    {
 140  30 List localizations = findLocalizationsForResource(baseResourceLocation, locale);
 141   
 142  30 Properties parent = null;
 143  30 Properties assembledProperties = null;
 144   
 145  30 Iterator i = localizations.iterator();
 146   
 147  30 while (i.hasNext())
 148    {
 149  78 ResourceLocalization rl = (ResourceLocalization) i.next();
 150   
 151  78 Locale l = rl.getLocale();
 152   
 153    // Retrieve namespace properties for current locale (and parent locales)
 154  78 Properties namespaceProperties = getNamespaceProperties(component, l);
 155   
 156    // Use the namespace properties as default for assembled properties
 157  78 assembledProperties = new Properties(namespaceProperties);
 158   
 159    // Read localized properties for component
 160  78 Properties properties = readComponentProperties(component, l, rl.getResource(), null);
 161   
 162    // Override parent properties with current locale
 163  78 if (parent != null) {
 164  40 if (properties != null)
 165  29 parent.putAll(properties);
 166    }
 167    else
 168  38 parent = properties;
 169   
 170    // Add to assembled properties
 171  78 if (parent != null)
 172  70 assembledProperties.putAll(parent);
 173   
 174    // Save result in cache
 175  78 propertiesMap.put(l, assembledProperties);
 176    }
 177   
 178  30 return assembledProperties;
 179    }
 180   
 181  70 private Properties assembleNamespaceProperties(INamespace namespace, Map propertiesMap,
 182    Locale locale)
 183    {
 184  70 List localizations = findLocalizationsForResource(
 185    namespace.getSpecificationLocation(),
 186    locale);
 187   
 188    // Build them back up in reverse order.
 189   
 190  70 Properties parent = _emptyProperties;
 191   
 192  70 Iterator i = localizations.iterator();
 193   
 194  70 while (i.hasNext())
 195    {
 196  138 ResourceLocalization rl = (ResourceLocalization) i.next();
 197   
 198  138 Locale l = rl.getLocale();
 199   
 200  138 Properties properties = (Properties) propertiesMap.get(l);
 201   
 202  138 if (properties == null)
 203    {
 204  70 properties = readNamespaceProperties(namespace, l, rl.getResource(), parent);
 205   
 206  70 propertiesMap.put(l, properties);
 207    }
 208   
 209  138 parent = properties;
 210    }
 211   
 212  70 return parent;
 213   
 214    }
 215   
 216    /**
 217    * Finds the localizations of the provided resource. Returns a List of
 218    * {@link ResourceLocalization}(each pairing a locale with a localized resource). The list is
 219    * ordered from most general (i.e., "foo.properties") to most specific (i.e.,
 220    * "foo_en_US_yokel.properties").
 221    */
 222   
 223  100 private List findLocalizationsForResource(Resource resource, Locale locale)
 224    {
 225  100 List result = new ArrayList();
 226   
 227  100 String baseName = extractBaseName(resource);
 228   
 229  100 LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, SUFFIX);
 230   
 231  100 while (g.more())
 232    {
 233  216 String localizedName = g.next();
 234  216 Locale l = g.getCurrentLocale();
 235  216 Resource localizedResource = resource.getRelativeResource(localizedName);
 236   
 237  216 result.add(new ResourceLocalization(l, localizedResource));
 238    }
 239   
 240  100 Collections.reverse(result);
 241   
 242  100 return result;
 243    }
 244   
 245  100 private String extractBaseName(Resource baseResourceLocation)
 246    {
 247  100 String fileName = baseResourceLocation.getName();
 248  100 int dotx = fileName.lastIndexOf('.');
 249   
 250  100 return fileName.substring(0, dotx);
 251    }
 252   
 253  78 private Properties readComponentProperties(IComponent component, Locale locale,
 254    Resource propertiesResource, Properties parent)
 255    {
 256  78 String encoding = getComponentMessagesEncoding(component, locale);
 257   
 258  78 return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
 259    }
 260   
 261  70 private Properties readNamespaceProperties(INamespace namespace, Locale locale,
 262    Resource propertiesResource, Properties parent)
 263    {
 264  70 String encoding = getNamespaceMessagesEncoding(namespace, locale);
 265   
 266  70 return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
 267    }
 268   
 269  148 private Properties readPropertiesResource(URL resourceURL, String encoding, Properties parent)
 270    {
 271  148 if (resourceURL == null)
 272  65 return parent;
 273   
 274  83 Properties result = new Properties(parent);
 275   
 276  83 LocalizedProperties wrapper = new LocalizedProperties(result);
 277   
 278  83 InputStream input = null;
 279   
 280  83 try
 281    {
 282  83 input = new BufferedInputStream(resourceURL.openStream());
 283   
 284  83 if (encoding == null)
 285  78 wrapper.load(input);
 286    else
 287  5 wrapper.load(input, encoding);
 288   
 289  83 input.close();
 290    }
 291    catch (IOException ex)
 292    {
 293  0 throw new ApplicationRuntimeException(ImplMessages.unableToLoadProperties(
 294    resourceURL,
 295    ex), ex);
 296    }
 297    finally
 298    {
 299  83 close(input);
 300    }
 301   
 302  83 return result;
 303    }
 304   
 305  83 private void close(InputStream is)
 306    {
 307  83 if (is != null)
 308  83 try
 309    {
 310  83 is.close();
 311    }
 312    catch (IOException ex)
 313    {
 314    // Ignore.
 315    }
 316    }
 317   
 318    /**
 319    * Clears the cache of read properties files.
 320    */
 321   
 322  0 public synchronized void resetEventDidOccur()
 323    {
 324  0 _componentCache.clear();
 325    }
 326   
 327  31 public Messages getMessages(IComponent component)
 328    {
 329  31 return new ComponentMessages(component.getPage().getLocale(),
 330    getLocalizedProperties(component));
 331    }
 332   
 333  78 private String getComponentMessagesEncoding(IComponent component, Locale locale)
 334    {
 335  78 String encoding = _componentPropertySource.getLocalizedComponentProperty(
 336    component,
 337    locale,
 338    MESSAGES_ENCODING_PROPERTY_NAME);
 339   
 340  78 if (encoding == null)
 341  76 encoding = _componentPropertySource.getLocalizedComponentProperty(
 342    component,
 343    locale,
 344    TemplateSourceImpl.TEMPLATE_ENCODING_PROPERTY_NAME);
 345   
 346  78 return encoding;
 347    }
 348   
 349  70 private String getNamespaceMessagesEncoding(INamespace namespace, Locale locale)
 350    {
 351  70 return _componentPropertySource.getLocalizedNamespaceProperty(
 352    namespace,
 353    locale,
 354    MESSAGES_ENCODING_PROPERTY_NAME);
 355    }
 356   
 357  24 public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
 358    {
 359  24 _componentPropertySource = componentPropertySource;
 360    }
 361    }