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}