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.common.internal.ArgUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017 018import java.io.*; 019import java.nio.file.*; 020import java.util.*; 021import java.util.regex.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.internal.*; 025 026/** 027 * Utility class for finding regular or localized files on the classpath and file system. 028 * 029 * <h5 class='section'>Example:</h5> 030 * <p class='bjava'> 031 * <jc>// Constructor a file source that looks for files in the "files" working directory, then in the 032 * // package "foo.bar", then in the package "foo.bar.files", then in the package "files".</jc> 033 * FileFinder <jv>finder</jv> = FileFinder 034 * .<jsm>create</jsm>() 035 * .dir(<js>"files"</js>) 036 * .cp(foo.bar.MyClass.<jk>class</jk>,<jk>null</jk>,<jk>true</jk>) 037 * .cp(foo.bar.MyClass.<jk>class</jk>,<js>"files"</js>,<jk>true</jk>) 038 * .cp(foo.bar.MyClass.<jk>class</jk>,<js>"/files"</js>,<jk>true</jk>) 039 * .cache(1_000_000l) <jc>// Cache files less than 1MB in size.</jc> 040 * .ignore(Pattern.<jsm>compile</jsm>(<js>"(?i)(.*\\.(class|properties))|(package.html)"</js>)) <jc>// Ignore certain files.</jc> 041 * .build(); 042 * 043 * <jc>// Find a normal file.</jc> 044 * InputStream <jv>is1</jv> = <jv>finder</jv>.getStream(<js>"text.txt"</js>); 045 * 046 * <jc>// Find a localized file called "text_ja_JP.txt".</jc> 047 * InputStream <jv>is2</jv> = <jv>finder</jv>.getStream(<js>"text.txt"</js>, Locale.<jsf>JAPAN</jsf>); 048 * </p> 049 * 050 * <p> 051 * If the <c>locale</c> is specified, then we look for resources whose name matches that locale. 052 * For example, if looking for the resource <js>"MyResource.txt"</js> for the Japanese locale, we will look for 053 * files in the following order: 054 * <ol> 055 * <li><js>"MyResource_ja_JP.txt"</js> 056 * <li><js>"MyResource_ja.txt"</js> 057 * <li><js>"MyResource.txt"</js> 058 * </ol> 059 * 060 * <p> 061 * The default implementation of this interface is {@link BasicFileFinder}. 062 * The {@link Builder#type(Class)} method is provided for instantiating other instances. 063 * 064 * <h5 class='section'>Example:</h5> 065 * <p class='bjava'> 066 * <jk>public class</jk> MyFileFinder <jk>extends</jk> BasicFileFinder { 067 * <jk>public</jk> MyFileFinder(FileFinder.Builder <jv>builder</jv>) { 068 * <jk>super</jk>(<jv>builder</jv>); 069 * } 070 * } 071 * 072 * <jc>// Instantiate subclass.</jc> 073 * FileFinder <jv>myFinder</jv> = FileFinder.<jsm>create</jsm>().type(MyFileFinder.<jk>class</jk>).build(); 074 * </p> 075 * 076 * <p> 077 * Subclasses must provide a public constructor that takes in any of the following arguments: 078 * <ul> 079 * <li>{@link Builder} - The builder object. 080 * <li>Any beans present in the bean store passed into the constructor. 081 * <li>Any {@link Optional} beans optionally present in bean store passed into the constructor. 082 * </ul> 083 * 084 * <h5 class='section'>See Also:</h5><ul> 085 * </ul> 086 */ 087public interface FileFinder { 088 089 //----------------------------------------------------------------------------------------------------------------- 090 // Static 091 //----------------------------------------------------------------------------------------------------------------- 092 093 /** Represents no file finder */ 094 public abstract class Void implements FileFinder {} 095 096 /** 097 * Static creator. 098 * 099 * @param beanStore The bean store to use for creating beans. 100 * @return A new builder for this object. 101 */ 102 public static Builder create(BeanStore beanStore) { 103 return new Builder(beanStore); 104 } 105 106 /** 107 * Static creator. 108 * 109 * @return A new builder for this object. 110 */ 111 public static Builder create() { 112 return new Builder(BeanStore.INSTANCE); 113 } 114 115 //----------------------------------------------------------------------------------------------------------------- 116 // Builder 117 //----------------------------------------------------------------------------------------------------------------- 118 119 /** 120 * Builder class. 121 */ 122 @FluentSetters 123 public static class Builder extends BeanBuilder<FileFinder> { 124 125 final Set<LocalDir> roots; 126 long cachingLimit; 127 Pattern[] include, exclude; 128 129 /** 130 * Constructor. 131 * 132 * @param beanStore The bean store to use for creating beans. 133 */ 134 protected Builder(BeanStore beanStore) { 135 super(BasicFileFinder.class, beanStore); 136 roots = set(); 137 cachingLimit = -1; 138 include = new Pattern[]{Pattern.compile(".*")}; 139 exclude = new Pattern[0]; 140 } 141 142 @Override /* BeanBuilder */ 143 protected FileFinder buildDefault() { 144 return new BasicFileFinder(this); 145 } 146 147 //------------------------------------------------------------------------------------------------------------- 148 // Properties 149 //------------------------------------------------------------------------------------------------------------- 150 151 /** 152 * Adds a class subpackage to the lookup paths. 153 * 154 * @param c The class whose package will be added to the lookup paths. Must not be <jk>null</jk>. 155 * @param path The absolute or relative subpath. 156 * @param recursive If <jk>true</jk>, also recursively adds all the paths of the parent classes as well. 157 * @return This object. 158 */ 159 @FluentSetter 160 public Builder cp(Class<?> c, String path, boolean recursive) { 161 assertArgNotNull("c", c); 162 while (c != null) { 163 roots.add(new LocalDir(c, path)); 164 c = recursive ? c.getSuperclass() : null; 165 } 166 return this; 167 } 168 169 /** 170 * Adds a file system directory to the lookup paths. 171 * 172 * @param path The path relative to the working directory. Must not be <jk>null</jk> 173 * @return This object. 174 */ 175 @FluentSetter 176 public Builder dir(String path) { 177 assertArgNotNull("path", path); 178 return path(Paths.get(".").resolve(path)); 179 } 180 181 /** 182 * Adds a file system directory to the lookup paths. 183 * 184 * @param path The directory path. 185 * @return This object. 186 */ 187 @FluentSetter 188 public Builder path(Path path) { 189 roots.add(new LocalDir(path)); 190 return this; 191 } 192 193 /** 194 * Enables in-memory caching of files for quicker retrieval. 195 * 196 * @param cachingLimit The maximum file size in bytes. 197 * @return This object. 198 */ 199 @FluentSetter 200 public Builder caching(long cachingLimit) { 201 this.cachingLimit = cachingLimit; 202 return this; 203 } 204 205 /** 206 * Specifies the regular expression file name patterns to use to include files being retrieved from the file source. 207 * 208 * @param patterns 209 * The regular expression include patterns. 210 * <br>The default is <js>".*"</js>. 211 * @return This object. 212 */ 213 @FluentSetter 214 public Builder include(String...patterns) { 215 this.include = alist(patterns).stream().map(x->Pattern.compile(x)).toArray(Pattern[]::new); 216 return this; 217 } 218 219 /** 220 * Specifies the regular expression file name pattern to use to exclude files from being retrieved from the file source. 221 * 222 * @param patterns 223 * The regular expression exclude patterns. 224 * <br>If none are specified, no files will be excluded. 225 * @return This object. 226 */ 227 @FluentSetter 228 public Builder exclude(String...patterns) { 229 this.exclude = alist(patterns).stream().map(x->Pattern.compile(x)).toArray(Pattern[]::new); 230 return this; 231 } 232 233 // <FluentSetters> 234 235 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 236 public Builder impl(Object value) { 237 super.impl(value); 238 return this; 239 } 240 241 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 242 public Builder type(Class<?> value) { 243 super.type(value); 244 return this; 245 } 246 247 // </FluentSetters> 248 } 249 250 //----------------------------------------------------------------------------------------------------------------- 251 // Instance 252 //----------------------------------------------------------------------------------------------------------------- 253 254 /** 255 * Returns the contents of the resource with the specified name. 256 * 257 * @param name The resource name. 258 * See {@link Class#getResource(String)} for format. 259 * @param locale 260 * The locale of the resource to retrieve. 261 * <br>If <jk>null</jk>, won't look for localized file names. 262 * @return The resolved resource contents, or <jk>null</jk> if the resource was not found. 263 * @throws IOException Thrown by underlying stream. 264 */ 265 public Optional<InputStream> getStream(String name, Locale locale) throws IOException; 266 267 /** 268 * Returns the file with the specified name as a string. 269 * 270 * @param name The file name. 271 * @param locale 272 * The locale of the resource to retrieve. 273 * <br>If <jk>null</jk>, won't look for localized file names. 274 * @return The contents of the file as a string. Assumes UTF-8 encoding. 275 * @throws IOException If file could not be read. 276 */ 277 public Optional<String> getString(String name, Locale locale) throws IOException; 278}