![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.cp.BasicFileFinder Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.cp;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.FileUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import java.io.*;
import java.util.*;
import java.util.ResourceBundle.*;
import java.util.concurrent.*;
import java.util.regex.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.internal.*;
/**
* Basic implementation of a {@link FileFinder}.
*
*
* Specialized behavior can be implemented by overridding the {@link #find(String, Locale)} method.
*
*
Example:
*
* public class MyFileFinder extends BasicFileFinder {
* @Override
* protected Optional<InputStream> find(String name , Locale locale ) throws IOException {
* // Do special handling or just call super.find().
* return super .find(name , locale );
* }
* }
*
*
* See Also:
*
*/
public class BasicFileFinder implements FileFinder {
private static final ResourceBundle.Control RB_CONTROL = ResourceBundle.Control.getControl(Control.FORMAT_DEFAULT);
private final Map files = new ConcurrentHashMap<>();
private final Map> localizedFiles = new ConcurrentHashMap<>();
private final LocalDir[] roots;
private final long cachingLimit;
private final Pattern[] include, exclude;
private final String[] includePatterns, excludePatterns;
private final int hashCode;
/**
* Builder-based constructor.
*
* @param builder The builder object.
*/
public BasicFileFinder(FileFinder.Builder builder) {
this.roots = builder.roots.toArray(new LocalDir[builder.roots.size()]);
this.cachingLimit = builder.cachingLimit;
this.include = builder.include;
this.exclude = builder.exclude;
this.includePatterns = alist(include).stream().map(Pattern::pattern).toArray(String[]::new);
this.excludePatterns = alist(exclude).stream().map(Pattern::pattern).toArray(String[]::new);
this.hashCode = HashCode.of(getClass(), roots, cachingLimit, includePatterns, excludePatterns);
}
/**
* Default constructor.
*
*
* Can be used when providing a subclass that overrides the {@link #find(String, Locale)} method.
*/
protected BasicFileFinder() {
this.roots = new LocalDir[0];
this.cachingLimit = -1;
this.include = new Pattern[0];
this.exclude = new Pattern[0];
this.includePatterns = new String[0];
this.excludePatterns = new String[0];
this.hashCode = HashCode.of(getClass(), roots, cachingLimit, includePatterns, excludePatterns);
}
//-----------------------------------------------------------------------------------------------------------------
// FileFinder methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* FileFinder */
public final Optional getStream(String name, Locale locale) throws IOException {
return find(name, locale);
}
@Override /* FileFinder */
public Optional getString(String name, Locale locale) throws IOException {
return optional(read(find(name, locale).orElse(null)));
}
//-----------------------------------------------------------------------------------------------------------------
// Implementation methods
//-----------------------------------------------------------------------------------------------------------------
/**
* The main implementation method for finding files.
*
*
* Subclasses can override this method to provide their own handling.
*
* @param name The resource name.
* See {@link Class#getResource(String)} for format.
* @param locale
* The locale of the resource to retrieve.
*
If null , won't look for localized file names.
* @return The resolved resource contents, or null if the resource was not found.
* @throws IOException Thrown by underlying stream.
*/
protected Optional find(String name, Locale locale) throws IOException {
name = StringUtils.trimSlashesAndSpaces(name);
if (isInvalidPath(name))
return empty();
if (locale != null)
localizedFiles.putIfAbsent(locale, new ConcurrentHashMap<>());
Map fileCache = locale == null ? files : localizedFiles.get(locale);
LocalFile lf = fileCache.get(name);
if (lf == null) {
List candidateFileNames = getCandidateFileNames(name, locale);
paths: for (LocalDir root : roots) {
for (String cfn : candidateFileNames) {
lf = root.resolve(cfn);
if (lf != null)
break paths;
}
}
if (lf != null && isIgnoredFile(lf.getName()))
lf = null;
if (lf != null) {
fileCache.put(name, lf);
if (cachingLimit >= 0) {
long size = lf.size();
if (size > 0 && size <= cachingLimit)
lf.cache();
}
}
}
return optional(lf == null ? null : lf.read());
}
/**
* Returns the candidate file names for the specified file name in the specified locale.
*
*
* For example, if looking for the "MyResource.txt" file in the Japanese locale, the iterator will return
* names in the following order:
*
* "MyResource_ja_JP.txt"
* "MyResource_ja.txt"
* "MyResource.txt"
*
*
*
* If the locale is null , then it will only return "MyResource.txt" .
*
* @param fileName The name of the file to get candidate file names on.
* @param locale
* The locale.
*
If null , won't look for localized file names.
* @return An iterator of file names to look at.
*/
protected List getCandidateFileNames(final String fileName, final Locale locale) {
if (locale == null)
return Collections.singletonList(fileName);
List list = new ArrayList<>();
String baseName = getBaseName(fileName);
String ext = getExtension(fileName);
getCandidateLocales(locale).forEach(x -> {
String ls = x.toString();
if (ls.isEmpty())
list.add(fileName);
else {
list.add(baseName + "_" + ls + (ext.isEmpty() ? "" : ('.' + ext)));
list.add(ls.replace('_', '/') + '/' + fileName);
}
});
return list;
}
/**
* Returns the candidate locales for the specified locale.
*
*
* For example, if locale is "ja_JP" , then this method will return:
*
* "ja_JP"
* "ja"
* ""
*
*
* @param locale The locale to get the list of candidate locales for.
* @return The list of candidate locales.
*/
protected List getCandidateLocales(Locale locale) {
return RB_CONTROL.getCandidateLocales("", locale);
}
/**
* Checks for path malformations such as use of ".." which can be used to open up security holes.
*
*
* Default implementation returns true if the path is any of the following:
*
* - Is blank or
null .
* - Contains
".." (to prevent traversing out of working directory).
* - Contains
"%" (to prevent URI trickery).
*
*
* @param path The path to check.
* @return true if the path is invalid.
*/
protected boolean isInvalidPath(String path) {
return isEmpty(path) || path.contains("..") || path.contains("%");
}
/**
* Returns true if the file should be ignored based on file name.
*
* @param name The name to check.
* @return true if the file should be ignored.
*/
protected boolean isIgnoredFile(String name) {
for (Pattern p : exclude)
if (p.matcher(name).matches())
return true;
for (Pattern p : include)
if (p.matcher(name).matches())
return false;
return true;
}
@Override
public int hashCode() {
return hashCode;
}
@Override /* Object */
public boolean equals(Object o) {
return o instanceof BasicFileFinder && eq(this, (BasicFileFinder)o, (x,y)->eq(x.hashCode, y.hashCode) && eq(x.getClass(), y.getClass()) && eq(x.roots, y.roots) && eq(x.cachingLimit, y.cachingLimit) && eq(x.includePatterns, y.includePatterns) && eq(x.excludePatterns, y.excludePatterns));
}
@Override /* Object */
public String toString() {
return filteredMap()
.append("class", getClass().getSimpleName())
.append("roots", roots)
.append("cachingLimit", cachingLimit)
.append("include", includePatterns)
.append("exclude", excludePatterns)
.append("hashCode", hashCode)
.asReadableString();
}
}