org.zaproxy.zap.utils.LocaleUtils Maven / Gradle / Ivy
Show all versions of zap Show documentation
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2010 The ZAP Development Team
*
* Licensed 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.zaproxy.zap.utils;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.zaproxy.zap.view.ViewLocale;
public final class LocaleUtils {
private static final Logger logger = Logger.getLogger(LocaleUtils.class);
private static final String MESSAGES_BASE_FILENAME = Constant.MESSAGES_PREFIX + "_";
private static final String DEFAULT_LOCALE = "en_GB";
private LocaleUtils() {}
/**
* Regular expression to match a language of a {@code Locale}.
*
* @see #COUNTRY_LOCALE_REGEX
* @since 2.4.0
*/
public static final String LANGUAGE_LOCALE_REGEX = "[a-zA-Z]{2,8}";
/**
* Regular expression to match a country/region of a {@code Locale}.
*
* @see #LANGUAGE_LOCALE_REGEX
* @since 2.4.0
*/
public static final String COUNTRY_LOCALE_REGEX = "[a-zA-Z]{2}|[0-9]{3}";
/**
* Finds and returns a localised resource, using the given {@code function} to validate it.
*
* The function is called for each resource until a non-{@code null} resource is returned or
* all resources are consumed. The resource name is built using the parameters {@code fileName},
* {@code fileExtension}, and the given {@code locale}. The candidates and fallback locale are
* obtained from a {@link ZapResourceBundleControl}.
*
*
For example, with the following parameters:
*
*
* - {@code fileName} - {@code org.zaproxy.zap.file}
*
- {@code fileExtension} - {@code ext}
*
- {@code locale} - {@code es_ES}
*
*
* and "en_GB" as fallback locale returned by the control, it would produce the following
* resource names:
*
* {@code
* org/zaproxy/zap/file_es_ES.ext
* org/zaproxy/zap/file_es.ext
* org/zaproxy/zap/file.ext
* org/zaproxy/zap/file_en_GB.ext
* org/zaproxy/zap/file_en.ext
* }
*
* The {@code function} could be, for example, the instance method {@code
* ClassLoader::getResource}.
*
* @param the type of the result.
* @param fileName the base file name.
* @param fileExtension the file extension.
* @param locale the target locale.
* @param function the function that validates and returns the resource.
* @return the resource, or {@code null} if none found.
* @since 2.8.0
*/
public static R findResource(
String fileName, String fileExtension, Locale locale, Function function) {
return findResource(fileName, fileExtension, "", locale, function);
}
/**
* Finds and returns a localised resource, using the given {@code function} to validate it.
*
* The function is called for each resource until a non-{@code null} resource is returned or
* all resources are consumed. The resource name is built using the parameters {@code fileName},
* {@code fileExtension}, and the given {@code locale}. The candidates and fallback locale are
* obtained from the given resource bundle control.
*
*
For example, with the following parameters:
*
*
* - {@code control} - {@code new ZapResourceBundleControl()}
*
- {@code fileName} - {@code org.zaproxy.zap.file}
*
- {@code fileExtension} - {@code ext}
*
- {@code locale} - {@code es_ES}
*
*
* and "en_GB" as fallback locale returned by the {@code control}, it would produce the
* following resource names:
*
* {@code
* org/zaproxy/zap/file_es_ES.ext
* org/zaproxy/zap/file_es.ext
* org/zaproxy/zap/file.ext
* org/zaproxy/zap/file_en_GB.ext
* org/zaproxy/zap/file_en.ext
* }
*
* The {@code function} could be, for example, the instance method {@code
* ClassLoader::getResource}.
*
* @param the type of the result.
* @param control the resource bundle control to obtain the candidate and fallback locales and
* build the resource names.
* @param fileName the base file name.
* @param fileExtension the file extension.
* @param locale the target locale.
* @param function the function that validates and returns the resource.
* @return the resource, or {@code null} if none found.
* @since 2.8.0
*/
public static R findResource(
ResourceBundle.Control control,
String fileName,
String fileExtension,
Locale locale,
Function function) {
return findResource(control, fileName, fileExtension, "", locale, function);
}
/**
* Finds and returns a localised resource, using the given {@code function} to validate it.
*
* The function is called for each resource until a non-{@code null} resource is returned or
* all resources are consumed. The resource name is built using the parameters {@code fileName},
* {@code fileExtension}, and the given {@code locale}. The {@code localeToken}, if non-{@code
* null} nor empty, is replaced in the {@code fileName} with the current candidate locale. The
* candidates and fallback locale are obtained from a {@link ZapResourceBundleControl}.
*
*
For example, with the following parameters:
*
*
* - {@code fileName} - {@code org.zaproxy.zap.dir%LC%.file}
*
- {@code fileExtension} - {@code ext}
*
- {@code localeToken} - {@code %LC%}
*
- {@code locale} - {@code es_ES}
*
*
* and "en_GB" as fallback locale returned by the control, it would produce the following
* resource names:
*
* {@code
* org/zaproxy/zap/dir_es_ES/file_es_ES.ext
* org/zaproxy/zap/dir_es/file_es.ext
* org/zaproxy/zap/dir/file.ext
* org/zaproxy/zap/dir_en_GB/file_en_GB.ext
* org/zaproxy/zap/dir_en/file_en.ext
* }
*
* The {@code function} could be, for example, the instance method {@code
* ClassLoader::getResource}.
*
* @param the type of the result.
* @param fileName the base file name.
* @param fileExtension the file extension.
* @param localeToken the token that represents the locale, to be replaced in the {@code
* fileName}. Might be {@code null}.
* @param locale the target locale.
* @param function the function that validates and returns the resource.
* @return the resource, or {@code null} if none found.
* @since 2.8.0
*/
public static R findResource(
String fileName,
String fileExtension,
String localeToken,
Locale locale,
Function function) {
return findResource(
new ZapResourceBundleControl(),
fileName,
fileExtension,
localeToken,
locale,
function);
}
/**
* Finds and returns a localised resource, using the given {@code function} to validate it.
*
* The function is called for each resource until a non-{@code null} resource is returned or
* all resources are consumed. The resource name is built using the parameters {@code fileName},
* {@code fileExtension}, and the given {@code locale}. The {@code localeToken}, if non-{@code
* null} nor empty, is replaced in the {@code fileName} with the current candidate locale. The
* candidates and fallback locale are obtained from the given resource bundle control.
*
*
For example, with the following parameters:
*
*
* - {@code control} - {@code new ZapResourceBundleControl()}
*
- {@code fileName} - {@code org.zaproxy.zap.dir%LC%.file}
*
- {@code fileExtension} - {@code ext}
*
- {@code localeToken} - {@code %LC%}
*
- {@code locale} - {@code es_ES}
*
*
* and "en_GB" as fallback locale returned by the {@code control}, it would produce the
* following resource names:
*
* {@code
* org/zaproxy/zap/dir_es_ES/file_es_ES.ext
* org/zaproxy/zap/dir_es/file_es.ext
* org/zaproxy/zap/dir/file.ext
* org/zaproxy/zap/dir_en_GB/file_en_GB.ext
* org/zaproxy/zap/dir_en/file_en.ext
* }
*
* The {@code function} could be, for example, the instance method {@code
* ClassLoader::getResource}.
*
* @param the type of the result.
* @param control the resource bundle control to obtain the candidate and fallback locales and
* build the resource names.
* @param fileName the base file name.
* @param fileExtension the file extension.
* @param localeToken the token that represents the locale, to be replaced in the {@code
* fileName}. Might be {@code null}.
* @param locale the target locale.
* @param function the function that validates and returns the resource.
* @return the resource, or {@code null} if none found.
* @since 2.8.0
*/
public static R findResource(
ResourceBundle.Control control,
String fileName,
String fileExtension,
String localeToken,
Locale locale,
Function function) {
Set candidateLocales = new LinkedHashSet<>();
candidateLocales.addAll(control.getCandidateLocales("", locale));
Locale fallbackLocale = control.getFallbackLocale("", locale);
if (fallbackLocale != null) {
candidateLocales.addAll(control.getCandidateLocales("", fallbackLocale));
}
for (Locale candidateLocale : candidateLocales) {
String strLocale = control.toBundleName("", candidateLocale);
String prefix = fileName;
if (localeToken != null && !localeToken.isEmpty()) {
prefix =
prefix.replaceAll(
Pattern.quote(localeToken), Matcher.quoteReplacement(strLocale));
}
R result = function.apply(control.toResourceName(prefix + strLocale, fileExtension));
if (result != null) {
return result;
}
}
return null;
}
/**
* Convenience method that calls the method {@code #createResourceFilePattern(String, String)},
* with parameters {@code Constant.MESSAGES_PREFIX} and {@code Constant.MESSAGES_EXTENSION},
* respectively.
*
* @return a {@code Pattern} that matches the Messages.properties files of different {@code
* Locale}s
* @see #createResourceFilesPattern(String, String)
* @see Constant#MESSAGES_PREFIX
* @see Constant#MESSAGES_EXTENSION
* @since 2.4.0
*/
public static Pattern createMessagesPropertiesFilePattern() {
return createResourceFilesPattern(Constant.MESSAGES_PREFIX, Constant.MESSAGES_EXTENSION);
}
/**
* Returns a regular expression to match source and translated resource filenames with the given
* {@code fileName} and {@code fileExtension}.
*
* For example, with {@code fileName} as "Messages" and {@code fileExtension} as
* ".properties" the returned pattern would match:
*
*
* - Messages.properties
*
- Messages_en.properties
*
- Messages_en_GB.properties
*
*
* @param fileName the name of the resource files
* @param fileExtension the extension of the resource files
* @return the regular expression to match resource filenames
* @throws IllegalArgumentException if the given {@code fileName} or {@code fileExtension} is
* {@code null}.
* @see #createResourceFilesPattern(String, String)
* @see #LANGUAGE_LOCALE_REGEX
* @see #COUNTRY_LOCALE_REGEX
* @since 2.4.0
*/
public static String createResourceFilesRegex(String fileName, String fileExtension) {
if (fileName == null) {
throw new IllegalArgumentException("Parameter fileName must not be null.");
}
if (fileExtension == null) {
throw new IllegalArgumentException("Parameter fileExtension must not be null.");
}
StringBuilder strBuilder =
new StringBuilder(
fileName.length()
+ LANGUAGE_LOCALE_REGEX.length()
+ COUNTRY_LOCALE_REGEX.length()
+ fileExtension.length()
+ 13);
strBuilder.append(Pattern.quote(fileName));
strBuilder.append("(?:_").append(LANGUAGE_LOCALE_REGEX);
strBuilder.append("(?:_").append(COUNTRY_LOCALE_REGEX).append(")?").append(")?");
strBuilder.append(Pattern.quote(fileExtension));
strBuilder.append('$');
return strBuilder.toString();
}
/**
* Returns a {@code Pattern} to match source and translated resource filenames with the given
* {@code fileName} and {@code fileExtension}.
*
* For example, with {@code fileName} as "Messages" and {@code fileExtension} as
* ".properties" the returned pattern would match:
*
*
* - Messages.properties
*
- Messages_en.properties
*
- Messages_en_GB.properties
*
*
* The pattern is case-sensitive.
*
* @param fileName the name of the resource files
* @param fileExtension the extension of the resource files
* @return the {@code Pattern} to match resource filenames
* @throws IllegalArgumentException if the given {@code fileName} or {@code fileExtension} is
* {@code null}.
* @see #createResourceFilesRegex(String, String)
* @see #LANGUAGE_LOCALE_REGEX
* @see #COUNTRY_LOCALE_REGEX
* @since 2.4.0
*/
public static Pattern createResourceFilesPattern(String fileName, String fileExtension) {
return Pattern.compile(createResourceFilesRegex(fileName, fileExtension));
}
/**
* Returns a list of languages and countries of the {@code Locale}s (as {@code String}, for
* example "en_GB"), of default language and available translations.
*
*
The list is sorted by language/country codes with default locale, always, at first
* position.
*
* @return The list of available translations, ZAP provides
*/
public static List getAvailableLocales() {
List locales = readAvailableLocales();
Collections.sort(locales);
// Always put English at the top
locales.add(0, DEFAULT_LOCALE);
return locales;
}
private static List readAvailableLocales() {
File dir = new File(Constant.getZapInstall(), Constant.LANG_DIR);
if (!dir.exists()) {
logger.debug(
"Skipping read of available locales, the directory does not exist: "
+ dir.getAbsolutePath());
return new ArrayList<>(0);
}
FilenameFilter filter = new MessagesPropertiesFilenameFilter();
String[] files = dir.list(filter);
if (files == null || files.length == 0) {
logger.warn("No Messages files in directory " + dir.getAbsolutePath());
return new ArrayList<>(0);
}
List locales = new ArrayList<>(files.length);
final int baseFilenameLength = MESSAGES_BASE_FILENAME.length();
for (String file : Arrays.asList(files)) {
if (file.startsWith(MESSAGES_BASE_FILENAME)) {
locales.add(file.substring(baseFilenameLength, file.indexOf(".")));
}
}
return locales;
}
/**
* Convenience method that creates a {@code ViewLocale} with the given {@code locale} and a
* display name created by calling {@code getLocalDisplayName(String)}, with the {@code locale}
* as argument.
*
* @param locale the locale that will used to create the {@code ViewLocale}
* @return the {@code ViewLocale} for the given locale
* @since 2.4.0
* @see #getLocalDisplayName(String)
*/
public static ViewLocale getViewLocale(String locale) {
return new ViewLocale(locale, getLocalDisplayName(locale));
}
/**
* Returns a list of {@code ViewLocale}s, sorted by display name, of the default language and
* available translations.
*
* @return the {@code ViewLocale}s of the default language and available translations.
* @see ViewLocale
* @since 2.4.0
*/
public static List getAvailableViewLocales() {
List locales = readAvailableLocales();
List localesUI = new ArrayList<>();
if (!locales.isEmpty()) {
for (String locale : locales) {
localesUI.add(new ViewLocale(locale, getLocalDisplayName(locale)));
}
Collections.sort(
localesUI,
new Comparator() {
@Override
public int compare(ViewLocale o1, ViewLocale o2) {
return o1.toString().compareTo(o2.toString());
}
});
}
// Always put English at the top
localesUI.add(0, new ViewLocale(DEFAULT_LOCALE, getLocalDisplayName(DEFAULT_LOCALE)));
return localesUI;
}
/**
* Gets the name of the language of and for the given locale.
*
* @param locale the locale whose language name will be returned
* @return the name of the language
*/
public static String getLocalDisplayName(String locale) {
String desc = "" + locale;
if (locale != null) {
String[] langArray = locale.split("_");
Locale loc = null;
if (langArray.length == 1) {
loc = new Locale(langArray[0]);
} else if (langArray.length == 2) {
loc = new Locale(langArray[0], langArray[1]);
} else if (langArray.length == 3) {
loc = new Locale(langArray[0], langArray[1], langArray[2]);
}
if (loc != null) {
desc = loc.getDisplayLanguage(loc);
}
}
return desc;
}
private static final class MessagesPropertiesFilenameFilter implements FilenameFilter {
private final Pattern messagesPropertiesPattern = createMessagesPropertiesFilePattern();
@Override
public boolean accept(File dir, String name) {
return messagesPropertiesPattern.matcher(name).matches();
}
}
}