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}