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.marshall.cp; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.io.*; 023import java.nio.file.*; 024import java.util.*; 025import java.util.regex.*; 026 027import org.apache.juneau.commons.inject.*; 028import org.apache.juneau.commons.io.*; 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 * To instantiate subclasses, create a subclass-specific builder and pass it to the subclass constructor. 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.Builder <jv>b</jv> = FileFinder.<jsm>create</jsm>(); 078 * FileFinder <jv>myFinder</jv> = <jk>new</jk> MyFileFinder(<jv>b</jv>); 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 */ 090@SuppressWarnings({ 091 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 092 "java:S9398" // Nested type is public API; moving it would break callers. 093}) 094public interface FileFinder { 095 096 /** 097 * Builder class. 098 */ 099 public static class Builder { 100 101 private final BeanStore beanStore; 102 final Set<LocalDir> roots; 103 long cachingLimit; 104 Pattern[] include; 105 Pattern[] exclude; 106 107 /** 108 * Constructor. 109 * 110 * @param beanStore The bean store to use for creating beans. 111 */ 112 protected Builder(BeanStore beanStore) { 113 this.beanStore = beanStore; 114 roots = set(); 115 cachingLimit = -1; 116 include = a(Pattern.compile(".*")); 117 exclude = a(); 118 } 119 120 /** 121 * Returns the bean store used by this builder. 122 * 123 * @return The bean store used by this builder. 124 */ 125 public BeanStore beanStore() { 126 return beanStore; 127 } 128 129 /** 130 * Enables in-memory caching of files for quicker retrieval. 131 * 132 * @param cachingLimit The maximum file size in bytes. 133 * @return This object. 134 */ 135 public Builder caching(long cachingLimit) { 136 this.cachingLimit = cachingLimit; 137 return this; 138 } 139 140 /** 141 * Adds a class subpackage to the lookup paths. 142 * 143 * @param c The class whose package will be added to the lookup paths. Must not be <jk>null</jk>. 144 * @param path The absolute or relative subpath. Can be <jk>null</jk> (the class's own package root is used with no subpath appended). 145 * @param recursive If <jk>true</jk>, also recursively adds all the paths of the parent classes as well. 146 * @return This object. 147 */ 148 @SuppressWarnings({ 149 "null" // Null check handled by reqnn 150 }) 151 public Builder cp(Class<?> c, String path, boolean recursive) { 152 reqnn("c", c); 153 while (nn(c)) { 154 roots.add(new LocalDir(c, path)); 155 c = recursive ? c.getSuperclass() : null; 156 } 157 return this; 158 } 159 160 /** 161 * Adds a file system directory to the lookup paths. 162 * 163 * @param value The path relative to the working directory. Must not be <jk>null</jk>. 164 * @return This object. 165 */ 166 public Builder dir(String value) { 167 reqnn("value", value); 168 return path(Paths.get(".").resolve(value)); 169 } 170 171 /** 172 * Specifies the regular expression file name pattern to use to exclude files from being retrieved from the file source. 173 * 174 * @param patterns 175 * The regular expression exclude patterns. 176 * <br>If none are specified, no files will be excluded. 177 * @return This object. 178 */ 179 public Builder exclude(String...patterns) { 180 this.exclude = l(patterns).stream().map(Pattern::compile).toArray(Pattern[]::new); 181 return this; 182 } 183 184 185 /** 186 * Specifies the regular expression file name patterns to use to include files being retrieved from the file source. 187 * 188 * @param patterns 189 * The regular expression include patterns. 190 * <br>The default is <js>".*"</js>. 191 * @return This object. 192 */ 193 public Builder include(String...patterns) { 194 this.include = l(patterns).stream().map(Pattern::compile).toArray(Pattern[]::new); 195 return this; 196 } 197 198 /** 199 * Adds a file system directory to the lookup paths. 200 * 201 * @param value The directory path. 202 * @return This object. 203 */ 204 public Builder path(Path value) { 205 roots.add(new LocalDir(value)); 206 return this; 207 } 208 209 /** 210 * Builds the file finder. 211 * 212 * @return A new {@link BasicFileFinder}. 213 */ 214 public FileFinder build() { 215 return new BasicFileFinder(this); 216 } 217 } 218 219 /** Represents no file finder */ 220 @SuppressWarnings({ 221 "java:S9398" // Public annotation-default marker; moving breaks API. 222 }) 223 public abstract class Void implements FileFinder {} 224 225 /** 226 * Static creator. 227 * 228 * @return A new builder for this object. 229 */ 230 static Builder create() { 231 return new Builder(BasicBeanStore.INSTANCE); 232 } 233 234 /** 235 * Static creator. 236 * 237 * @param beanStore The bean store to use for creating beans. 238 * @return A new builder for this object. 239 */ 240 static Builder create(BeanStore beanStore) { 241 return new Builder(beanStore); 242 } 243 244 /** 245 * Returns the contents of the resource with the specified name. 246 * 247 * @param name The resource name. 248 * See {@link Class#getResource(String)} for format. 249 * @param locale 250 * The locale of the resource to retrieve. 251 * <br>If <jk>null</jk>, won't look for localized file names. 252 * @return The resolved resource contents, or an empty {@link Optional} if the resource was not found. 253 * @throws IOException Thrown by underlying stream. 254 */ 255 Optional<InputStream> getStream(String name, Locale locale) throws IOException; 256 257 /** 258 * Returns the file with the specified name as a string. 259 * 260 * @param name The file name. 261 * @param locale 262 * The locale of the resource to retrieve. 263 * <br>If <jk>null</jk>, won't look for localized file names. 264 * @return The contents of the file as a string. Assumes UTF-8 encoding. 265 * @throws IOException If file could not be read. 266 */ 267 Optional<String> getString(String name, Locale locale) throws IOException; 268}