All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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: *

    *
  1. "MyResource_ja_JP.txt" *
  2. "MyResource_ja.txt" *
  3. "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: *

    *
  1. "ja_JP" *
  2. "ja" *
  3. "" *
* * @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(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy