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.cp; 014 015import static org.apache.juneau.internal.IOUtils.*; 016import static org.apache.juneau.internal.ObjectUtils.*; 017 018import java.io.*; 019import java.util.*; 020import java.util.concurrent.*; 021 022import org.apache.juneau.internal.*; 023import org.apache.juneau.parser.*; 024 025/** 026 * Class for retrieving and caching resource files from the classpath. 027 */ 028public final class ResourceManager { 029 030 // Maps resource names+locales to found resources. 031 private final ConcurrentHashMap<ResourceKey,byte[]> byteCache; 032 private final ConcurrentHashMap<ResourceKey,String> stringCache; 033 034 private final Class<?> baseClass; 035 private final ResourceFinder resourceFinder; 036 private final boolean useCache; 037 038 /** 039 * Constructor. 040 * 041 * @param baseClass The default class to use for retrieving resources from the classpath. 042 * @param resourceFinder The resource finder implementation. 043 * @param useCache If <jk>true</jk>, retrieved resources are stored in an in-memory cache for fast lookup. 044 */ 045 public ResourceManager(Class<?> baseClass, ResourceFinder resourceFinder, boolean useCache) { 046 this.baseClass = baseClass; 047 this.resourceFinder = resourceFinder; 048 this.useCache = useCache; 049 if (useCache) { 050 this.byteCache = new ConcurrentHashMap<>(); 051 this.stringCache = new ConcurrentHashMap<>(); 052 } else { 053 this.byteCache = null; 054 this.stringCache = null; 055 } 056 } 057 058 /** 059 * Constructor. 060 * 061 * <p> 062 * Uses default {@link BasicResourceFinder} for finding resources. 063 * 064 * @param baseClass The default class to use for retrieving resources from the classpath. 065 */ 066 public ResourceManager(Class<?> baseClass) { 067 this(baseClass, BasicResourceFinder.INSTANCE, false); 068 } 069 070 /** 071 * Finds the resource with the given name. 072 * 073 * @param name Name of the desired resource. 074 * @return An input stream to the object, or <jk>null</jk> if the resource could not be found. 075 * @throws IOException Thrown by underlying stream. 076 */ 077 public InputStream getStream(String name) throws IOException { 078 return getStream(name, null); 079 } 080 081 /** 082 * Finds the resource with the given name for the specified locale and returns it as an input stream. 083 * 084 * @param name Name of the desired resource. 085 * @param locale The locale. Can be <jk>null</jk>. 086 * @return An input stream to the object, or <jk>null</jk> if the resource could not be found. 087 * @throws IOException Thrown by underlying stream. 088 */ 089 public InputStream getStream(String name, Locale locale) throws IOException { 090 091 if (isEmpty(name)) 092 return null; 093 094 if (! useCache) 095 return resourceFinder.findResource(baseClass, name, locale); 096 097 ResourceKey key = new ResourceKey(name, locale); 098 099 byte[] r = byteCache.get(key); 100 if (r == null) { 101 try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) { 102 if (is != null) 103 byteCache.putIfAbsent(key, IOUtils.readBytes(is, 1024)); 104 } 105 } 106 107 r = byteCache.get(key); 108 return r == null ? null : new ByteArrayInputStream(r); 109 } 110 111 /** 112 * Finds the resource with the given name and converts it to a simple string. 113 * 114 * @param name Name of the desired resource. 115 * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found. 116 * @throws IOException Thrown by underlying stream. 117 */ 118 public String getString(String name) throws IOException { 119 return getString(name, null); 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 * @param locale The locale. Can be <jk>null</jk>. 127 * @return The resource converted to a string, or <jk>null</jk> if the resource could not be found. 128 * @throws IOException Thrown by underlying stream. 129 */ 130 public String getString(String name, Locale locale) throws IOException { 131 132 if (isEmpty(name)) 133 return null; 134 135 if (! useCache) { 136 try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) { 137 return IOUtils.read(is, IOUtils.UTF8); 138 } 139 } 140 141 ResourceKey key = new ResourceKey(name, locale); 142 143 String r = stringCache.get(key); 144 if (r == null) { 145 try (InputStream is = resourceFinder.findResource(baseClass, name, locale)) { 146 if (is != null) 147 stringCache.putIfAbsent(key, IOUtils.read(is, IOUtils.UTF8)); 148 } 149 } 150 151 return stringCache.get(key); 152 } 153 154 /** 155 * Reads the input stream and parses it into a POJO using the specified parser. 156 * 157 * @param c The class type of the POJO to create. 158 * @param parser The parser to use to parse the stream. 159 * @param name The resource name (e.g. "htdocs/styles.css"). 160 * @return The parsed resource, or <jk>null</jk> if the resource could not be found. 161 * @throws IOException Thrown by underlying stream. 162 * @throws ParseException If stream could not be parsed using the specified parser. 163 */ 164 public <T> T getResource(Class<T> c, Parser parser, String name) throws IOException, ParseException { 165 return getResource(c, parser, name, null); 166 } 167 168 /** 169 * Reads the input stream and parses it into a POJO using the specified parser. 170 * 171 * @param c The class type of the POJO to create. 172 * @param parser The parser to use to parse the stream. 173 * @param name The resource name (e.g. "htdocs/styles.css"). 174 * @param locale 175 * Optional locale. 176 * <br>If <jk>null</jk>, won't look for localized file names. 177 * @return The parsed resource, or <jk>null</jk> if the resource could not be found. 178 * @throws IOException Thrown by underlying stream. 179 * @throws ParseException If stream could not be parsed using the specified parser. 180 */ 181 public <T> T getResource(Class<T> c, Parser parser, String name, Locale locale) throws IOException, ParseException { 182 183 InputStream is = getStream(name, locale); 184 if (is == null) 185 return null; 186 try (Closeable in = parser.isReaderParser() ? new InputStreamReader(is, UTF8) : is) { 187 return parser.parse(in, c); 188 } 189 } 190 191 private class ResourceKey { 192 final String name; 193 final Locale locale; 194 195 ResourceKey(String name, Locale locale) { 196 this.name = name; 197 this.locale = locale; 198 } 199 200 @Override 201 public int hashCode() { 202 return name.hashCode() + (locale == null ? 0 : locale.hashCode()); 203 } 204 205 @Override 206 public boolean equals(Object o) { 207 return eq(this, (ResourceKey)o, (x,y)->eq(x.name, y.name) && eq(x.locale, y.locale)); 208 } 209 } 210}