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