001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.utils;
014
015import java.io.*;
016import java.util.*;
017import java.util.concurrent.*;
018
019import org.apache.juneau.internal.*;
020
021/**
022 * Class for retrieving and caching resource files from the classpath.
023 */
024public final class ClasspathResourceManager {
025
026   // Maps resource names+locales to found resources.
027   private final ConcurrentHashMap<ResourceKey,byte[]> byteCache;
028   private final ConcurrentHashMap<ResourceKey,String> stringCache;
029
030   private final Class<?> baseClass;
031   private final ClasspathResourceFinder resourceFinder;
032   private final boolean useCache;
033
034   /**
035    * Constructor.
036    *
037    * @param baseClass The default class to use for retrieving resources from the classpath.
038    * @param resourceFinder The resource finder implementation.
039    * @param useCache If <jk>true</jk>, retrieved resources are stored in an in-memory cache for fast lookup.
040    */
041   public ClasspathResourceManager(Class<?> baseClass, ClasspathResourceFinder resourceFinder, boolean useCache) {
042      this.baseClass = baseClass;
043      this.resourceFinder = resourceFinder;
044      this.useCache = useCache;
045      this.byteCache = useCache ? new ConcurrentHashMap<ResourceKey,byte[]>() : null;
046      this.stringCache = useCache ? new ConcurrentHashMap<ResourceKey,String>() : null;
047   }
048
049   /**
050    * Constructor.
051    *
052    * <p>
053    * Uses default {@link ClasspathResourceFinderBasic} for finding resources.
054    *
055    * @param baseClass The default class to use for retrieving resources from the classpath.
056    */
057   public ClasspathResourceManager(Class<?> baseClass) {
058      this(baseClass, new ClasspathResourceFinderBasic(), false);
059   }
060
061   /**
062    * Finds the resource with the given name.
063    *
064    * @param name Name of the desired resource.
065    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
066    * @throws IOException
067    */
068   public InputStream getStream(String name) throws IOException {
069      return getStream(name, null);
070   }
071
072   /**
073    * Finds the resource with the given name for the specified locale and returns it as an input stream.
074    *
075    * @param name Name of the desired resource.
076    * @param locale The locale.  Can be <jk>null</jk>.
077    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
078    * @throws IOException
079    */
080   public InputStream getStream(String name, Locale locale) throws IOException {
081      return getStream(baseClass, name, locale);
082   }
083
084   /**
085    * Finds the resource with the given name for the specified locale and returns it as an input stream.
086    *
087    * @param baseClass
088    *    Overrides the default class to use for retrieving the classpath resource.
089    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
090    * @param name Name of the desired resource.
091    * @param locale The locale.  Can be <jk>null</jk>.
092    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
093    * @throws IOException
094    */
095   public InputStream getStream(Class<?> baseClass, String name, Locale locale) throws IOException {
096
097      if (baseClass == null)
098         baseClass = this.baseClass;
099
100      if (! useCache)
101         return resourceFinder.findResource(baseClass, name, locale);
102
103      ResourceKey key = new ResourceKey(name, locale);
104
105      byte[] r = byteCache.get(key);
106      if (r == null) {
107         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
108            if (is != null)
109               byteCache.putIfAbsent(key, IOUtils.readBytes(is, 1024));
110         }
111      }
112
113      r = byteCache.get(key);
114      return r == null ? null : new ByteArrayInputStream(r);
115   }
116
117   /**
118    * Finds the resource with the given name and converts it to a simple string.
119    *
120    * @param name Name of the desired resource.
121    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
122    * @throws IOException
123    */
124   public String getString(String name) throws IOException {
125      return getString(baseClass, name, null);
126   }
127
128   /**
129    * Finds the resource with the given name and converts it to a simple string.
130    *
131    * @param baseClass
132    *    Overrides the default class to use for retrieving the classpath resource.
133    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
134    * @param name Name of the desired resource.
135    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
136    * @throws IOException
137    */
138   public String getString(Class<?> baseClass, String name) throws IOException {
139      return getString(baseClass, name, null);
140   }
141
142   /**
143    * Finds the resource with the given name and converts it to a simple string.
144    *
145    * @param name Name of the desired resource.
146    * @param locale The locale.  Can be <jk>null</jk>.
147    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
148    * @throws IOException
149    */
150   public String getString(String name, Locale locale) throws IOException {
151      return getString(baseClass, name, locale);
152   }
153
154   /**
155    * Finds the resource with the given name and converts it to a simple string.
156    *
157    * @param baseClass
158    *    Overrides the default class to use for retrieving the classpath resource.
159    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
160    * @param name Name of the desired resource.
161    * @param locale The locale.  Can be <jk>null</jk>.
162    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
163    * @throws IOException
164    */
165   public String getString(Class<?> baseClass, String name, Locale locale) throws IOException {
166
167      if (baseClass == null)
168         baseClass = this.baseClass;
169
170      if (! useCache) {
171         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
172            return IOUtils.read(is, IOUtils.UTF8);
173         }
174      }
175
176      ResourceKey key = new ResourceKey(name, locale);
177
178      String r = stringCache.get(key);
179      if (r == null) {
180         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
181            if (is != null)
182               stringCache.putIfAbsent(key, IOUtils.read(is, IOUtils.UTF8));
183         }
184      }
185
186      return stringCache.get(key);
187   }
188
189   private class ResourceKey {
190      final String name;
191      final Locale locale;
192
193      ResourceKey(String name, Locale locale) {
194         this.name = name;
195         this.locale = locale;
196      }
197
198      @Override
199      public int hashCode() {
200         return name.hashCode() + (locale == null ? 0 : locale.hashCode());
201      }
202
203      @Override
204      public boolean equals(Object o) {
205         if (o == null)
206            return false;
207         ResourceKey ok = (ResourceKey)o;
208         return ObjectUtils.equals(name, ok.name) && ObjectUtils.equals(locale, ok.locale);
209      }
210   }
211}