
de.codecamp.messages.shared.conf.ProjectConf Maven / Gradle / Ivy
package de.codecamp.messages.shared.conf;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import de.codecamp.messages.shared.bundle.MessageBundleManager;
import de.codecamp.messages.shared.bundle.NioFileSystemAdapter;
import de.codecamp.messages.shared.messageformat.DefaultMessageFormatSupport;
import de.codecamp.messages.shared.messageformat.IcuMessageFormatSupport;
import de.codecamp.messages.shared.model.MessageModule;
/**
* {@link ProjectConf} contains the aspects of the project configuration shared by the annotation
* processor and other tools like the Eclipse companion plug-in. It represents a snapshot of the
* current configuration, so it's not intended to be long lived or stored for later use.
*/
public class ProjectConf
implements
MessageModule
{
public static final String ERROR_CONF_FILE_ERROR = "ProjectConfFileError";
public static final String ERROR_MISSING_OPTION = "MissingOption";
public static final String ERROR_ILLEGAL_LOCALE = "IllegalLocale";
public static final String ERROR_ILLEGAL_BUNDLE_MAPPING = "IllegalBundleMapping";
public static final String ERROR_ILLEGAL_TYPE_ABBREVIATION = "IllegalTypeAbbreviation";
public static final String ERROR_ILLEGAL_IMPORT = "IllegalImport";
public static final String ERROR_MISSING_IMPORT = "MissingImport";
public static final String ERROR_UNKNOWN_VALUE = "UnknownValue";
public static final String PROJECT_CONF_FILE_NAME = "messages-conf.properties";
public static final String CONF_PREFIX = "messages.";
public static final String CONF_PROJECT_DIR = CONF_PREFIX + "projectDir";
public static final String CONF_MODULE_NAME = CONF_PREFIX + "moduleName";
public static final String CONF_BUNDLE_DIR = CONF_PREFIX + "bundleDir";
public static final String BUNDLE_DIR_DEFAULT = "src/main/resources/messages";
public static final String CONF_BUNDLE_ENCODING = CONF_PREFIX + "bundleEncoding";
public static final String CONF_BUNDLES = CONF_PREFIX + "bundles";
private static final String BUNDLES_DEFAULT = ":messages";
private static final String BUNDLES_IMPORTS = "$imports";
public static final String CONF_IGNORED_BUNDLES = CONF_PREFIX + "ignoredBundles";
public static final String CONF_TARGET_LOCALES = CONF_PREFIX + "targetLocales";
public static final String CONF_IMPORTS = CONF_PREFIX + "imports";
public static final String CONF_TYPE_ABBREVIATIONS = CONF_PREFIX + "typeAbbreviations";
public static final String CONF_MODE = CONF_PREFIX + "mode";
public static final String CONF_MISSING_MESSAGE_POLICY = CONF_PREFIX + "missingMessagePolicy";
public static final String CONF_MESSAGE_ARG_POLICY = CONF_PREFIX + "messageArgPolicy";
public static final String CONF_UNDECLARED_KEY_POLICY = CONF_PREFIX + "undeclaredKeyPolicy";
public static final String CONF_UNDECLARED_KEY_COMMENT = CONF_PREFIX + "undeclaredKeyComment";
private static final String UNDECLARED_KEY_COMMENT_DEFAULT = "FIXME undeclared message key";
public static final String CONF_BUNDLE_MISMATCH_POLICY = CONF_PREFIX + "bundleMismatchPolicy";
public static final String CONF_MESSAGE_FORMAT = CONF_PREFIX + "messageFormat";
public static final String CONF_GENERATE_CONSTANTS = CONF_PREFIX + "generateConstants";
public static final String CONF_GENERATE_PROXIES = CONF_PREFIX + "generateProxies";
public static final List ALL_CONF_NAMES;
static
{
List confNames = Arrays.asList(CONF_MODULE_NAME, CONF_PROJECT_DIR, CONF_BUNDLE_DIR,
CONF_BUNDLE_ENCODING, CONF_BUNDLES, CONF_IGNORED_BUNDLES, CONF_TARGET_LOCALES, CONF_IMPORTS,
CONF_MODE, CONF_MISSING_MESSAGE_POLICY, CONF_MESSAGE_ARG_POLICY, CONF_UNDECLARED_KEY_POLICY,
CONF_UNDECLARED_KEY_COMMENT, CONF_BUNDLE_MISMATCH_POLICY, CONF_MESSAGE_FORMAT,
CONF_GENERATE_CONSTANTS, CONF_GENERATE_PROXIES);
ALL_CONF_NAMES = Collections.unmodifiableList(confNames);
}
private final String moduleName;
private final String projectDir;
private final String bundleDir;
private final Charset bundleEncoding;
private final Set ignoredBundles;
private final List bundleMappings;
private final List targetLocales;
private final List imports;
private final Map typeAbbreviations;
private final Mode mode;
private final MissingMessagePolicy missingMessagePolicy;
private final MessageArgPolicy messageArgPolicy;
private final UndeclaredKeyPolicy undeclaredKeyPolicy;
private final String undeclaredKeyComment;
private final BundleMismatchPolicy bundleMismatchPolicy;
private final String messageFormat;
private final boolean generateConstants;
private final boolean generateProxies;
private List resolvedBundleMappings;
public ProjectConf(ConfValueProvider confProvider)
throws ProjectConfException
{
this.projectDir =
parseString(confProvider.getConf(CONF_PROJECT_DIR), CONF_PROJECT_DIR, true, null);
ConfValueProvider extConfProvider = null;
if (projectDir != null)
{
Path messagesPropsPath = Paths.get(projectDir, PROJECT_CONF_FILE_NAME);
if (Files.isRegularFile(messagesPropsPath))
{
try (InputStream in = Files.newInputStream(messagesPropsPath))
{
Properties messagesProperties = new Properties();
messagesProperties.load(in);
extConfProvider = (name) -> {
String value = confProvider.getConf(name);
if (value == null)
value = messagesProperties.getProperty(name);
if (value == null)
value = messagesProperties.getProperty(StringUtils.removeStart(name, CONF_PREFIX));
return value;
};
}
catch (IOException ex)
{
String msg = "Failed to read %s in project directory '%s'.";
msg = String.format(msg, PROJECT_CONF_FILE_NAME, projectDir);
throw new ProjectConfException(msg, ex, ERROR_CONF_FILE_ERROR, projectDir);
}
}
}
if (extConfProvider == null)
extConfProvider = confProvider;
this.moduleName = parseString(extConfProvider.getConf(CONF_MODULE_NAME), CONF_MODULE_NAME, true,
"application");
this.bundleEncoding = parseBundleEncoding(extConfProvider.getConf(CONF_BUNDLE_ENCODING));
this.ignoredBundles = parseIgnoredBundles(extConfProvider.getConf(CONF_IGNORED_BUNDLES));
this.bundleMappings = parseBundles(extConfProvider.getConf(CONF_BUNDLES), getModuleName());
this.targetLocales = parseLocales(extConfProvider.getConf(CONF_TARGET_LOCALES));
this.imports = parseImports(extConfProvider.getConf(CONF_IMPORTS));
this.typeAbbreviations =
parseTypeAbbreviations(extConfProvider.getConf(CONF_TYPE_ABBREVIATIONS));
this.mode = parseEnum(extConfProvider.getConf(CONF_MODE), Mode.class, Mode.RELEASE);
this.missingMessagePolicy = parseEnum(extConfProvider.getConf(CONF_MISSING_MESSAGE_POLICY),
MissingMessagePolicy.class, mode.missingMessagePolicy());
this.messageArgPolicy = parseEnum(extConfProvider.getConf(CONF_MESSAGE_ARG_POLICY),
MessageArgPolicy.class, mode.messageArgPolicy());
this.undeclaredKeyPolicy = parseEnum(extConfProvider.getConf(CONF_UNDECLARED_KEY_POLICY),
UndeclaredKeyPolicy.class, mode.undeclaredKeyPolicy());
this.undeclaredKeyComment = parseString(extConfProvider.getConf(CONF_UNDECLARED_KEY_COMMENT),
CONF_UNDECLARED_KEY_COMMENT, true, UNDECLARED_KEY_COMMENT_DEFAULT);
this.bundleMismatchPolicy = parseEnum(extConfProvider.getConf(CONF_BUNDLE_MISMATCH_POLICY),
BundleMismatchPolicy.class, mode.bundleMismatchPolicy());
this.bundleDir = parseString(extConfProvider.getConf(CONF_BUNDLE_DIR), CONF_BUNDLE_DIR, true,
BUNDLE_DIR_DEFAULT);
this.messageFormat = parseString(extConfProvider.getConf(CONF_MESSAGE_FORMAT), CONF_BUNDLE_DIR,
true, DefaultMessageFormatSupport.ID);
if (messageFormat != null && !messageFormat.equals(DefaultMessageFormatSupport.ID)
&& !messageFormat.equals(IcuMessageFormatSupport.ID))
{
String msg = "Unknown value '%s' for %s.";
msg = String.format(msg, messageFormat, CONF_MESSAGE_FORMAT);
throw new ProjectConfException(msg, ERROR_UNKNOWN_VALUE, messageFormat, CONF_MESSAGE_FORMAT);
}
this.generateConstants = parseBoolean(extConfProvider.getConf(CONF_GENERATE_CONSTANTS), true);
this.generateProxies = parseBoolean(extConfProvider.getConf(CONF_GENERATE_PROXIES), true);
}
public String getProjectDir()
{
return projectDir;
}
@Override
public String getModuleName()
{
return moduleName;
}
public String getBundleDir()
{
if (projectDir != null)
{
return Paths.get(projectDir, bundleDir).toString();
}
else
{
return bundleDir;
}
}
public Charset getBundleEncoding()
{
return bundleEncoding;
}
public Set getIgnoredBundles()
{
return ignoredBundles;
}
@Override
public List getBundleMappings()
{
return bundleMappings;
}
@Override
public List getTargetLocales()
{
return targetLocales;
}
@Override
public List getImportedModules()
{
return getImports();
}
public List getImports()
{
return imports;
}
public Map getTypeAbbreviations()
{
return typeAbbreviations;
}
public Mode getMode()
{
return mode;
}
public MissingMessagePolicy getMissingMessagePolicy()
{
return missingMessagePolicy;
}
public MessageArgPolicy getMessageArgPolicy()
{
return messageArgPolicy;
}
public UndeclaredKeyPolicy getUndeclaredKeyPolicy()
{
return undeclaredKeyPolicy;
}
public String getUndeclaredKeyComment()
{
return undeclaredKeyComment;
}
public BundleMismatchPolicy getBundleMismatchPolicy()
{
return bundleMismatchPolicy;
}
@Override
public String getMessageFormat()
{
return messageFormat;
}
public boolean getGenerateConstants()
{
return generateConstants;
}
public boolean getGenerateProxies()
{
return generateProxies;
}
// private static String parseString(String rawValue, String optionKey, boolean blankToNull)
// throws ProjectConfException
// {
// String value = parseString(rawValue, optionKey, blankToNull, null);
// if (value == null)
// {
// String msg = "Option '%s' must be set.";
// msg = String.format(msg, optionKey);
// throw new ProjectConfException(msg, ERROR_MISSING_OPTION, optionKey);
// }
//
// return value;
// }
private static String parseString(String rawValue, String optionKey, boolean blankToNull,
String defaultValue)
throws ProjectConfException
{
String value = rawValue;
if (blankToNull)
value = StringUtils.defaultIfBlank(value, null);
value = StringUtils.trim(value);
if (value == null)
{
value = defaultValue;
}
return value;
}
private static Charset parseBundleEncoding(String rawValue)
throws ProjectConfException
{
if (StringUtils.isBlank(rawValue))
return StandardCharsets.UTF_8;
rawValue = StringUtils.trim(rawValue);
try
{
return Charset.forName(rawValue);
}
catch (IllegalCharsetNameException | UnsupportedCharsetException ex)
{
throw (ProjectConfException) new ProjectConfException(
"Message bundle encoding '%s' not supported.", ERROR_UNKNOWN_VALUE, rawValue)
.initCause(ex);
}
}
/**
* @param rawValue
* the raw value
* @param moduleName
* the module name
* @return the message bundle mappings, sorted from longest to shortest package
* @throws IllegalArgumentException
*/
private static List parseBundles(String rawValue, String moduleName)
throws ProjectConfException
{
if (StringUtils.isBlank(rawValue))
rawValue = BUNDLES_DEFAULT;
List bundleMappings = new ArrayList<>();
for (String bundleToken : StringUtils.split(rawValue, ","))
{
bundleToken = bundleToken.trim();
if (bundleToken.equals(BUNDLES_IMPORTS))
{
bundleMappings.add(BundleMapping.IMPORTS_PLACEHOLDER);
continue;
}
if (!bundleToken.contains(":"))
{
String msg = "Illegal message bundle mapping: %s";
msg = String.format(msg, bundleToken);
throw new ProjectConfException(msg, ERROR_ILLEGAL_BUNDLE_MAPPING, bundleToken);
}
String[] tokens = StringUtils.splitPreserveAllTokens(bundleToken, ":", 2);
String packageName = tokens[0].trim();
String bundleName = tokens[1].trim();
if (StringUtils.isNotBlank(moduleName))
{
/*
* According to Javadoc of ResourceBundle, base names (=bundle names) should always be fully
* qualified class names. I.e. they should be using '.' instead of '/', even though '/' will
* work for Properties-based resource bundles.
*/
bundleName = (moduleName + "." + bundleName).replace('/', '.');
}
if (bundleToken.contains("_"))
{
String msg =
"Underscore (_) now allowed within message bundle name. It's reserved for the locale: %s";
msg = String.format(msg, bundleToken);
throw new ProjectConfException(msg, ERROR_ILLEGAL_BUNDLE_MAPPING, bundleName);
}
bundleMappings.add(new BundleMapping(packageName, bundleName));
}
return ImmutableList.copyOf(bundleMappings);
}
private static Set parseIgnoredBundles(String rawValue)
{
if (StringUtils.isBlank(rawValue))
return Collections.emptySet();
Set ignoredBundles = Stream.of(StringUtils.split(rawValue, ","))
.filter(StringUtils::isNotBlank).map(StringUtils::trim).collect(toSet());
return ImmutableSet.copyOf(ignoredBundles);
}
/**
* @param rawValue
* the raw value
* @return the list of required locales
* @throws IllegalArgumentException
*/
private static List parseLocales(String rawValue, Locale... defaults)
throws ProjectConfException
{
List locales = new ArrayList<>();
if (!StringUtils.isBlank(rawValue))
{
List localeStrings = Stream.of(StringUtils.split(rawValue, ","))
.filter(StringUtils::isNotBlank).map(StringUtils::trim).collect(toList());
for (String localeString : localeStrings)
{
Locale locale = Locale.forLanguageTag(localeString);
if (locale.getLanguage().isEmpty() && localeString.contains("_"))
locale = LocaleUtils.toLocale(localeString);
if (locale.getLanguage().isEmpty())
{
String msg = "The locale string '%s' did not contain a language.";
msg = String.format(msg, localeString);
throw new ProjectConfException(msg, ERROR_ILLEGAL_LOCALE, localeString);
}
locales.add(locale);
}
}
if (locales.isEmpty())
locales.addAll(Arrays.asList(defaults));
if (locales.isEmpty())
{
String msg = "Option '%s' must be set.";
msg = String.format(msg, CONF_TARGET_LOCALES);
throw new ProjectConfException(msg, ERROR_MISSING_OPTION, CONF_TARGET_LOCALES);
}
return ImmutableList.copyOf(locales);
}
private List parseImports(String rawValue)
throws ProjectConfException
{
if (StringUtils.isBlank(rawValue))
return Collections.emptyList();
List imports = Stream.of(StringUtils.split(rawValue, ","))
.filter(StringUtils::isNotBlank).map(StringUtils::trim).collect(toList());
return ImmutableList.copyOf(imports);
}
private static Map parseTypeAbbreviations(String rawValue)
throws ProjectConfException
{
if (StringUtils.isBlank(rawValue))
return Collections.emptyMap();
Map typeAbbreviations = new HashMap<>();
for (String abbreviationItem : StringUtils.split(rawValue, ","))
{
abbreviationItem = abbreviationItem.trim();
if (!abbreviationItem.contains(":"))
{
String msg = "Illegal type abbreviation: %s";
msg = String.format(msg, abbreviationItem);
throw new ProjectConfException(msg, ERROR_ILLEGAL_TYPE_ABBREVIATION, abbreviationItem);
}
String[] tokens = StringUtils.splitPreserveAllTokens(abbreviationItem, ":", 2);
String abbreviation = tokens[0].trim();
String fullyQualifiedType = tokens[1].trim();
typeAbbreviations.put(abbreviation, fullyQualifiedType);
}
return ImmutableMap.copyOf(typeAbbreviations);
}
private > T parseEnum(String rawValue, Class enumType)
throws ProjectConfException
{
if (StringUtils.isEmpty(rawValue))
{
return null;
}
else
{
try
{
return Enum.valueOf(enumType, rawValue);
}
catch (IllegalArgumentException ex)
{
String msg = "Unknown value '%s' for %s.";
msg = String.format(msg, rawValue, enumType.getSimpleName());
throw new ProjectConfException(msg, ERROR_UNKNOWN_VALUE, rawValue,
enumType.getSimpleName());
}
}
}
private > T parseEnum(String rawValue, Class enumType, T defaultValue)
throws ProjectConfException
{
T e = parseEnum(rawValue, enumType);
if (e == null)
e = defaultValue;
return e;
}
private Boolean parseBoolean(String rawValue, Boolean defaultValue)
{
Boolean result = BooleanUtils.toBooleanObject(rawValue, "true", "false", null);
if (result == null)
result = defaultValue;
return result;
}
public void resolveBundleMappings(Function importedModuleProvider)
throws ProjectConfException
{
resolvedBundleMappings = getImportedBundleMappings(this, importedModuleProvider);
}
private static List getImportedBundleMappings(MessageModule module,
Function importedModuleProvider)
throws ProjectConfException
{
List bundleMappings = new ArrayList<>(module.getBundleMappings());
List importedBundleMappings = new ArrayList<>();
if (module.getImportedModules() != null)
{
for (String importedModuleName : module.getImportedModules())
{
MessageModule importedModule = importedModuleProvider.apply(importedModuleName);
if (importedModule == null)
{
String msg = "Imported messages module '%s' not found.";
msg = String.format(msg, importedModuleName);
throw new ProjectConfException(msg, ERROR_MISSING_IMPORT, importedModuleName);
}
importedBundleMappings
.addAll(getImportedBundleMappings(importedModule, importedModuleProvider));
}
}
int placeholderIndex = bundleMappings.indexOf(BundleMapping.IMPORTS_PLACEHOLDER);
if (placeholderIndex > -1)
{
bundleMappings.remove(placeholderIndex);
bundleMappings.addAll(placeholderIndex, importedBundleMappings);
}
else
{
bundleMappings.addAll(importedBundleMappings);
}
return bundleMappings;
}
/**
* Returns the most specific bundle name for the given message key after applying the bundle root
* mappings.
*
* @param messageKey
* a message key
* @return the target bundle name for the given message key or null if no mapping is available
*/
public Optional toTargetBundleName(String messageKey)
{
return toTargetBundleName(messageKey, false);
}
public Optional toTargetBundleName(String messageKey, boolean onlyLocalMappings)
{
if (!onlyLocalMappings && resolvedBundleMappings == null)
throw new IllegalStateException("Message bundle mappings have not been resolved.");
List mappings;
if (onlyLocalMappings)
mappings = bundleMappings;
else
mappings = resolvedBundleMappings;
for (BundleMapping mapping : mappings)
{
Pattern messageKeyPattern = mapping.getMessageKeyPatternAsRegex();
Matcher matcher = messageKeyPattern.matcher(messageKey);
if (matcher.matches())
{
String bundleName = mapping.getBundleNamePattern();
if (bundleName.contains("*") && matcher.groupCount() >= 1)
{
String subPackage = StringUtils.removeStart(matcher.group(1), ".");
subPackage = StringUtils.substringBefore(subPackage, ".");
bundleName = mapping.getBundleNamePattern().replace("*", subPackage);
}
return Optional.of(bundleName);
}
}
return Optional.empty();
}
public MessageBundleManager createDefaultMessageBundleManager()
{
return new MessageBundleManager<>(this, new NioFileSystemAdapter());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy