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}