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