package org.springframework.roo.converters;
import static org.springframework.roo.project.LogicalPath.MODULE_PATH_SEPARATOR;
import static org.springframework.roo.shell.OptionContexts.FEATURE;
import static org.springframework.roo.shell.OptionContexts.UPDATE;
import static org.springframework.roo.shell.OptionContexts.UPDATELAST;
import static org.springframework.roo.support.util.AnsiEscapeCode.FG_CYAN;
import static org.springframework.roo.support.util.AnsiEscapeCode.decorate;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.ModuleFeatureName;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.shell.Completion;
import org.springframework.roo.shell.Converter;
import org.springframework.roo.shell.MethodTarget;
/**
* A {@link Converter} for {@link JavaPackage}s, with support for using
* {@value #TOP_LEVEL_PACKAGE_SYMBOL} to denote the user's top-level package.
*
* @author Ben Alex
* @author Paula Navarro
* @since 1.0
*/
@Component
@Service
public class JavaPackageConverter implements Converter<JavaPackage> {
/**
* The shell character that represents the current project or module's top
* level Java package.
*/
public static final String TOP_LEVEL_PACKAGE_SYMBOL = "~";
final Pattern pattern = Pattern.compile(FEATURE + "\\[(.+?)\\]");
@Reference
FileManager fileManager;
@Reference
LastUsed lastUsed;
@Reference
ProjectOperations projectOperations;
@Reference
TypeLocationService typeLocationService;
public JavaPackage convertFromText(String value, final Class<?> requiredType,
final String optionContext) {
if (StringUtils.isBlank(value)) {
return null;
}
Pom module = projectOperations.getFocusedModule();
if (value.contains(MODULE_PATH_SEPARATOR)) {
final String moduleName = value.substring(0, value.indexOf(MODULE_PATH_SEPARATOR));
module = projectOperations.getPomFromModuleName(moduleName);
value = value.substring(value.indexOf(MODULE_PATH_SEPARATOR) + 1, value.length()).trim();
if (StringUtils.contains(optionContext, UPDATE)) {
projectOperations.setModule(module);
}
}
String moduleName = module == null ? null : module.getModuleName();
JavaPackage result =
new JavaPackage(convertToFullyQualifiedPackageName(module, value), moduleName);
if (optionContext != null
&& (optionContext.contains(UPDATE) || optionContext.contains(UPDATELAST))) {
lastUsed.setPackage(result, module);
}
return result;
}
private String convertToFullyQualifiedPackageName(final Pom module, final String text) {
final String normalisedText = StringUtils.removeEnd(text, ".").toLowerCase();
if (normalisedText.startsWith(TOP_LEVEL_PACKAGE_SYMBOL)) {
return replaceTopLevelPackageSymbol(module, normalisedText);
}
return normalisedText;
}
public boolean getAllPossibleValues(final List<Completion> completions,
final Class<?> requiredType, final String existingData, final String optionContext,
final MethodTarget target) {
if (!projectOperations.isFocusedProjectAvailable()) {
return false;
}
Pom targetModule = null;
String heading = "";
String prefix = "";
String formattedPrefix = "";
if (existingData != null && existingData.contains(MODULE_PATH_SEPARATOR)) {
// Looking for a type in another module
final String targetModuleName =
existingData.substring(0, existingData.indexOf(MODULE_PATH_SEPARATOR));
// Validate feature
if (validateModule(projectOperations.getPomFromModuleName(targetModuleName), optionContext)) {
targetModule = projectOperations.getPomFromModuleName(targetModuleName);
heading = "";
prefix = targetModuleName + MODULE_PATH_SEPARATOR;
formattedPrefix = decorate(targetModuleName + MODULE_PATH_SEPARATOR, FG_CYAN);
}
} else {
// Validate module has installed the features required
if (validateModule(projectOperations.getFocusedModule(), optionContext)) {
// Looking for a type in the currently focused module
targetModule = projectOperations.getFocusedModule();
heading = targetModule.getModuleName();
prefix = "";
formattedPrefix = "";
}
}
addCompletionsForOtherModuleNames(completions, targetModule, optionContext);
if (targetModule != null && !"pom".equals(targetModule.getPackaging())) {
addCompletionsForPackagesInTargetModule(completions, targetModule, heading, prefix,
formattedPrefix);
}
return false;
}
private boolean validateModule(Pom module, String optionContext) {
if (optionContext != null) {
final Matcher matcher = pattern.matcher(optionContext);
if (matcher.find()) {
ModuleFeatureName moduleFeatureName = ModuleFeatureName.valueOf(matcher.group(1));
return typeLocationService.hasModuleFeature(module, moduleFeatureName);
}
}
return true;
}
private void addCompletionsForPackagesInTargetModule(final Collection<Completion> completions,
final Pom targetModule, final String heading, final String prefix,
final String formattedPrefix) {
final String topLevelPackage = typeLocationService.getTopLevelPackageForModule(targetModule);
completions.add(new Completion(prefix + topLevelPackage, formattedPrefix + topLevelPackage,
heading, 1));
for (final JavaType javaType : typeLocationService.getTypesForModule(targetModule)) {
String type = javaType.getFullyQualifiedTypeName();
completions.add(new Completion(prefix + type.substring(0, type.lastIndexOf('.')),
formattedPrefix + type.substring(0, type.lastIndexOf('.')), heading, 1));
}
}
private void addCompletionsForOtherModuleNames(final Collection<Completion> completions,
final Pom targetModule, String optionContext) {
for (final Pom pom : getValidModules(optionContext)) {
if (StringUtils.isNotBlank(pom.getModuleName())
&& (targetModule == null || !pom.getModuleName().equals(targetModule.getModuleName()))) {
completions.add(new Completion(pom.getModuleName() + MODULE_PATH_SEPARATOR, decorate(
pom.getModuleName() + MODULE_PATH_SEPARATOR, FG_CYAN), "Modules", 0));
}
}
}
private Collection<Pom> getValidModules(String optionContext) {
if (optionContext != null) {
final Matcher matcher = pattern.matcher(optionContext);
if (matcher.find()) {
ModuleFeatureName moduleFeatureName = ModuleFeatureName.valueOf(matcher.group(1));
return typeLocationService.getModules(moduleFeatureName);
}
}
return projectOperations.getPoms();
}
private String getTopLevelPackage(final Pom module) {
if (projectOperations.isProjectAvailable(module.getModuleName())) {
return typeLocationService.getTopLevelPackageForModule(module);
}
// Shouldn't happen if there's a project, i.e. most of the time
return "";
}
/**
* Replaces the {@link #TOP_LEVEL_PACKAGE_SYMBOL} at the beginning of the
* given text with the module's top-level package
*
* @param moduleName
* @param text
* @return a well-formed Java package name (might have a trailing dot)
*/
private String replaceTopLevelPackageSymbol(final Pom module, final String text) {
final String topLevelPackage = getTopLevelPackage(module);
if (TOP_LEVEL_PACKAGE_SYMBOL.equals(text)) {
return topLevelPackage;
}
final String textWithoutSymbol = StringUtils.removeStart(text, TOP_LEVEL_PACKAGE_SYMBOL);
return topLevelPackage + "." + StringUtils.removeStart(textWithoutSymbol, ".");
}
public boolean supports(final Class<?> requiredType, final String optionContext) {
return JavaPackage.class.isAssignableFrom(requiredType);
}
}