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      if (useCache) {
046         this.byteCache = new ConcurrentHashMap<>();
047         this.stringCache = new ConcurrentHashMap<>();
048      } else {
049         this.byteCache = null;
050         this.stringCache = null;
051      }
052   }
053
054   /**
055    * Constructor.
056    *
057    * <p>
058    * Uses default {@link ClasspathResourceFinderBasic} for finding resources.
059    *
060    * @param baseClass The default class to use for retrieving resources from the classpath.
061    */
062   public ClasspathResourceManager(Class<?> baseClass) {
063      this(baseClass, new ClasspathResourceFinderBasic(), false);
064   }
065
066   /**
067    * Finds the resource with the given name.
068    *
069    * @param name Name of the desired resource.
070    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
071    * @throws IOException Thrown by underlying stream.
072    */
073   public InputStream getStream(String name) throws IOException {
074      return getStream(name, null);
075   }
076
077   /**
078    * Finds the resource with the given name for the specified locale and returns it as an input stream.
079    *
080    * @param name Name of the desired resource.
081    * @param locale The locale.  Can be <jk>null</jk>.
082    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
083    * @throws IOException Thrown by underlying stream.
084    */
085   public InputStream getStream(String name, Locale locale) throws IOException {
086      return getStream(baseClass, name, locale);
087   }
088
089   /**
090    * Finds the resource with the given name for the specified locale and returns it as an input stream.
091    *
092    * @param baseClass
093    *    Overrides the default class to use for retrieving the classpath resource.
094    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
095    * @param name Name of the desired resource.
096    * @param locale The locale.  Can be <jk>null</jk>.
097    * @return An input stream to the object, or <jk>null</jk> if the resource could not be found.
098    * @throws IOException Thrown by underlying stream.
099    */
100   public InputStream getStream(Class<?> baseClass, String name, Locale locale) throws IOException {
101
102      if (baseClass == null)
103         baseClass = this.baseClass;
104
105      if (! useCache)
106         return resourceFinder.findResource(baseClass, name, locale);
107
108      ResourceKey key = new ResourceKey(name, locale);
109
110      byte[] r = byteCache.get(key);
111      if (r == null) {
112         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
113            if (is != null)
114               byteCache.putIfAbsent(key, IOUtils.readBytes(is, 1024));
115         }
116      }
117
118      r = byteCache.get(key);
119      return r == null ? null : new ByteArrayInputStream(r);
120   }
121
122   /**
123    * Finds the resource with the given name and converts it to a simple string.
124    *
125    * @param name Name of the desired resource.
126    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
127    * @throws IOException Thrown by underlying stream.
128    */
129   public String getString(String name) throws IOException {
130      return getString(baseClass, name, null);
131   }
132
133   /**
134    * Finds the resource with the given name and converts it to a simple string.
135    *
136    * @param baseClass
137    *    Overrides the default class to use for retrieving the classpath resource.
138    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
139    * @param name Name of the desired resource.
140    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
141    * @throws IOException Thrown by underlying stream.
142    */
143   public String getString(Class<?> baseClass, String name) throws IOException {
144      return getString(baseClass, name, null);
145   }
146
147   /**
148    * Finds the resource with the given name and converts it to a simple string.
149    *
150    * @param name Name of the desired resource.
151    * @param locale The locale.  Can be <jk>null</jk>.
152    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
153    * @throws IOException Thrown by underlying stream.
154    */
155   public String getString(String name, Locale locale) throws IOException {
156      return getString(baseClass, name, locale);
157   }
158
159   /**
160    * Finds the resource with the given name and converts it to a simple string.
161    *
162    * @param baseClass
163    *    Overrides the default class to use for retrieving the classpath resource.
164    *    <br>If <jk>null<jk>, uses the base class passed in through the constructor of this class.
165    * @param name Name of the desired resource.
166    * @param locale The locale.  Can be <jk>null</jk>.
167    * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found.
168    * @throws IOException Thrown by underlying stream.
169    */
170   public String getString(Class<?> baseClass, String name, Locale locale) throws IOException {
171
172      if (baseClass == null)
173         baseClass = this.baseClass;
174
175      if (! useCache) {
176         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
177            return IOUtils.read(is, IOUtils.UTF8);
178         }
179      }
180
181      ResourceKey key = new ResourceKey(name, locale);
182
183      String r = stringCache.get(key);
184      if (r == null) {
185         try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) {
186            if (is != null)
187               stringCache.putIfAbsent(key, IOUtils.read(is, IOUtils.UTF8));
188         }
189      }
190
191      return stringCache.get(key);
192   }
193
194   private class ResourceKey {
195      final String name;
196      final Locale locale;
197
198      ResourceKey(String name, Locale locale) {
199         this.name = name;
200         this.locale = locale;
201      }
202
203      @Override
204      public int hashCode() {
205         return name.hashCode() + (locale == null ? 0 : locale.hashCode());
206      }
207
208      @Override
209      public boolean equals(Object o) {
210         if (o == null)
211            return false;
212         ResourceKey ok = (ResourceKey)o;
213         return ObjectUtils.equals(name, ok.name) && ObjectUtils.equals(locale, ok.locale);
214      }
215   }
216}