001/*
002 * Units of Measurement Implementation for Java SE
003 * Copyright (c) 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tec.uom.se.internal.format.l10n;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.util.Enumeration;
035import java.util.HashMap;
036import java.util.Map;
037import java.util.PropertyResourceBundle;
038import java.util.ResourceBundle;
039import java.util.Set;
040import java.util.Vector;
041
042/**
043 * Extends <code>ResourceBundle</code> with 2 new capabilities. The first is to store the path where the properties file used to create the
044 * <code>InputStream</code> is located and the second is to allow additional <code>ResourceBundle</code> properties to be merged into an instance.
045 * </p>
046 * <p>
047 * To allow a <code>SystemOfUnits</code> to locate and merge extension module properties files.
048 * </p>
049 * 
050 * @author Werner Keil
051 */
052public class MultiPropertyResourceBundle extends ResourceBundle {
053
054  /**
055   * <p>
056   * The location of the properties file that was used to instantiate the <code>MultiPropertyResourceBundle</code> instance. This field is set by the
057   * constructor.
058   * </p>
059   */
060  private String resourcePath = null;
061
062  /**
063   * @return The location of the properties file that was used to instantiate the <code>MultiPropertyResourceBundle</code> instance.
064   */
065  public String getResourcePath() {
066    return resourcePath;
067  }
068
069  /**
070   * <p>
071   * A {@link Map} containing all the properties that have been merged from multiple {@link ResourceBundle} instances.
072   * </p>
073   */
074  private final Map<String, Object> resources = new HashMap<>();
075
076  /**
077   * <p>
078   * A {@link StringBuilder} instance containing all the paths of the {@link ResourceBundle} instances that have been merged into this instance. This
079   * value is intended to be use to help generate a key for caching JSON formatted resource output in the {@link AbstractWebScript} class.
080   * </p>
081   */
082  private final StringBuilder mergedBundlePaths = new StringBuilder();
083
084  /**
085   * @return Returns the {@link StringBuilder} instance containing the paths of all the {@link ResourceBundle} instances that have been merged into
086   *         this instance.
087   */
088  public StringBuilder getMergedBundlePaths() {
089    return mergedBundlePaths;
090  }
091
092  /**
093   * <p>
094   * Instantiates a new <code>MultiPropertyResourceBundle</code>.
095   * </p>
096   * 
097   * @param stream
098   *          The <code>InputStream</code> passed on to the super class constructor.
099   * @param resourcePath
100   *          The location of the properties file used to create the <code>InputStream</code>
101   * @throws IOException
102   */
103  public MultiPropertyResourceBundle(InputStream stream, String resourcePath) throws IOException {
104    final ResourceBundle resourceBundle = new PropertyResourceBundle(stream);
105    this.resourcePath = resourcePath;
106    merge(resourceBundle, resourcePath);
107  }
108
109  /**
110   * <p>
111   * Constructor for instantiating from an existing {@link ResourceBundle}. This calls the <code>merge</code> method to copy the properties from the
112   * bundle into the <code>resources</code> map.
113   * 
114   * @param baseBundle
115   * @param resourcePath
116   */
117  public MultiPropertyResourceBundle(ResourceBundle baseBundle, String resourcePath) {
118    super();
119    this.resourcePath = resourcePath;
120    merge(baseBundle, resourcePath);
121  }
122
123  /**
124   * <p>
125   * Merges the properties of a <code>ResourceBundle</code> into the current <code>MultiPropertyResourceBundle</code> instance. This will override any
126   * values mapped to duplicate keys in the current merged properties.
127   * </p>
128   * 
129   * @param resourceBundle
130   *          The <code>ResourceBundle</code> to merge the properties of.
131   * @param resourcePath
132   */
133  public void merge(ResourceBundle resourceBundle, String resourcePath) {
134    if (resourceBundle != null) {
135      Enumeration<String> keys = resourceBundle.getKeys();
136      while (keys.hasMoreElements()) {
137        String key = keys.nextElement();
138        this.resources.put(key, resourceBundle.getObject(key));
139      }
140    }
141
142    // Update the paths merged in this bundle
143    mergedBundlePaths.append(resourcePath);
144    mergedBundlePaths.append(":");
145  }
146
147  /**
148   * <p>
149   * Overrides the super class implementation to return an object located in the merged bundles
150   * </p>
151   * 
152   * @return An <code>Object</code> from the merged bundles
153   */
154  @Override
155  public Object handleGetObject(String key) {
156    if (key == null) {
157      throw new NullPointerException();
158    }
159    return this.resources.get(key);
160  }
161
162  /**
163   * <p>
164   * Overrides the super class implementation to return an enumeration of keys from all the merged bundles
165   * </p>
166   * 
167   * @return An <code>Enumeration</code> of the keys across all the merged bundles.
168   */
169  @Override
170  public Enumeration<String> getKeys() {
171    Vector<String> keys = new Vector<>(this.resources.keySet());
172    return keys.elements();
173  }
174
175  /**
176   * <p>
177   * Overrides the super class implementation to return the <code>Set</code> of keys from all merged bundles
178   * </p>
179   * 
180   * @return A <code>Set</code> of keys obtained from all merged bundles
181   */
182  @Override
183  protected Set<String> handleKeySet() {
184    return this.resources.keySet();
185  }
186
187  /**
188   * <p>
189   * Overrides the super class implementation to check the existence of a key across all merged bundles
190   * </p>
191   * 
192   * @return <code>true</code> if the key is present and <code>false</code> otherwise.
193   */
194  @Override
195  public boolean containsKey(String key) {
196    return this.resources.containsKey(key);
197  }
198
199  /**
200   * <p>
201   * Overrides the super class implementation to return the <code>Set</code> of keys from all merged bundles
202   * </p>
203   * 
204   * @return A <code>Set</code> of keys obtained from all merged bundles
205   */
206  @Override
207  public Set<String> keySet() {
208    return this.resources.keySet();
209  }
210}