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}