001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
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-385, Indriya nor the names of their 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 tech.units.indriya.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 * @version 1.0
052 */
053public class MultiPropertyResourceBundle extends ResourceBundle {
054// also see https://github.com/vitorzachi/tcc-multitenancy/blob/master/tccMultitenancy/src/net/sf/trugger/util/MultiResourceBundle.java but that code might be older
055        
056  /**
057   * <p>
058   * The location of the properties file that was used to instantiate the <code>MultiPropertyResourceBundle</code> instance. This field is set by the
059   * constructor.
060   * </p>
061   */
062  private String resourcePath = null;
063
064  /**
065   * @return The location of the properties file that was used to instantiate the <code>MultiPropertyResourceBundle</code> instance.
066   */
067  public String getResourcePath() {
068    return resourcePath;
069  }
070
071  /**
072   * <p>
073   * A {@link Map} containing all the properties that have been merged from multiple {@link ResourceBundle} instances.
074   * </p>
075   */
076  private final Map<String, Object> resources = new HashMap<>();
077
078  /**
079   * <p>
080   * A {@link StringBuilder} instance containing all the paths of the {@link ResourceBundle} instances that have been merged into this instance. This
081   * value is intended to be use to help generate a key for caching JSON formatted resource output in the {@link AbstractWebScript} class.
082   * </p>
083   */
084  private final StringBuilder mergedBundlePaths = new StringBuilder();
085
086  /**
087   * @return Returns the {@link StringBuilder} instance containing the paths of all the {@link ResourceBundle} instances that have been merged into
088   *         this instance.
089   */
090  public StringBuilder getMergedBundlePaths() {
091    return mergedBundlePaths;
092  }
093
094  /**
095   * <p>
096   * Instantiates a new <code>MultiPropertyResourceBundle</code>.
097   * </p>
098   * 
099   * @param stream
100   *          The <code>InputStream</code> passed on to the super class constructor.
101   * @param resourcePath
102   *          The location of the properties file used to create the <code>InputStream</code>
103   * @throws IOException
104   */
105  public MultiPropertyResourceBundle(InputStream stream, String resourcePath) throws IOException {
106    final ResourceBundle resourceBundle = new PropertyResourceBundle(stream);
107    this.resourcePath = resourcePath;
108    merge(resourceBundle, resourcePath);
109  }
110
111  /**
112   * <p>
113   * Constructor for instantiating from an existing {@link ResourceBundle}. This calls the <code>merge</code> method to copy the properties from the
114   * bundle into the <code>resources</code> map.
115   * 
116   * @param baseBundle
117   * @param resourcePath
118   */
119  public MultiPropertyResourceBundle(ResourceBundle baseBundle, String resourcePath) {
120    super();
121    this.resourcePath = resourcePath;
122    merge(baseBundle, resourcePath);
123  }
124
125  /**
126   * <p>
127   * Merges the properties of a <code>ResourceBundle</code> into the current <code>MultiPropertyResourceBundle</code> instance. This will override any
128   * values mapped to duplicate keys in the current merged properties.
129   * </p>
130   * 
131   * @param resourceBundle
132   *          The <code>ResourceBundle</code> to merge the properties of.
133   * @param aResourcePath
134   */
135  public void merge(ResourceBundle resourceBundle, String aResourcePath) {
136    if (resourceBundle != null) {
137      Enumeration<String> keys = resourceBundle.getKeys();
138      while (keys.hasMoreElements()) {
139        String key = keys.nextElement();
140        this.resources.put(key, resourceBundle.getObject(key));
141      }
142    }
143
144    // Update the paths merged in this bundle
145    mergedBundlePaths.append(aResourcePath);
146    mergedBundlePaths.append(":");
147  }
148
149  /**
150   * <p>
151   * Overrides the super class implementation to return an object located in the merged bundles
152   * </p>
153   * 
154   * @return An <code>Object</code> from the merged bundles
155   */
156  @Override
157  public Object handleGetObject(String key) {
158    if (key == null) {
159      throw new NullPointerException();
160    }
161    return this.resources.get(key);
162  }
163
164  /**
165   * <p>
166   * Overrides the super class implementation to return an enumeration of keys from all the merged bundles
167   * </p>
168   * 
169   * @return An <code>Enumeration</code> of the keys across all the merged bundles.
170   */
171  @Override
172  public Enumeration<String> getKeys() {
173    Vector<String> keys = new Vector<>(this.resources.keySet());
174    return keys.elements();
175  }
176
177  /**
178   * <p>
179   * Overrides the super class implementation to return the <code>Set</code> of keys from all merged bundles
180   * </p>
181   * 
182   * @return A <code>Set</code> of keys obtained from all merged bundles
183   */
184  @Override
185  protected Set<String> handleKeySet() {
186    return this.resources.keySet();
187  }
188
189  /**
190   * <p>
191   * Overrides the super class implementation to check the existence of a key across all merged bundles
192   * </p>
193   * 
194   * @return <code>true</code> if the key is present and <code>false</code> otherwise.
195   */
196  @Override
197  public boolean containsKey(String key) {
198    return this.resources.containsKey(key);
199  }
200
201  /**
202   * <p>
203   * Overrides the super class implementation to return the <code>Set</code> of keys from all merged bundles
204   * </p>
205   * 
206   * @return A <code>Set</code> of keys obtained from all merged bundles
207   */
208  @Override
209  public Set<String> keySet() {
210    return this.resources.keySet();
211  }
212}