package org.elixir_lang.action;
import com.google.common.base.CaseFormat;
import com.intellij.ide.actions.CreateFileFromTemplateDialog;
import com.intellij.ide.actions.CreateFromTemplateAction;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.fileTemplates.FileTemplateUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.InputValidatorEx;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import org.elixir_lang.icons.ElixirIcons;
import org.elixir_lang.psi.ElixirFile;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.openapi.util.Pair.pair;
/**
* Created by zyuyou on 15/7/7.
*/
public class CreateElixirModuleAction extends CreateFromTemplateAction<ElixirFile> {
/*
* CONSTANTS
*/
private static final String NEW_ELIXIR_MODULE = "New Elixir Module";
private static final String ALIAS_REGEXP = "[A-Z][0-9a-zA-Z_]*";
private static final String MODULE_NAME_REGEXP = ALIAS_REGEXP + "(\\." + ALIAS_REGEXP + ")*";
private static final Pattern MODULE_NAME_PATTERN = Pattern.compile(MODULE_NAME_REGEXP);
private static final String DESCRIPTION = "Nested Aliases, like Foo.Bar.Baz, are created in subdirectory for the " +
"parent Aliases, foo/bar/Baz.ex";
private static final String EXTENSION = ".ex";
private static final String EXISTING_MODULE_MESSAGE_FMT = "'%s' already exists";
private static final String INVALID_MODULE_MESSAGE_FMT = "'%s' is not a valid Elixir module name. Elixir module " +
"names should be a dot-separated-sequence of alphanumeric (and underscore) Aliases, each starting with a " +
"capital letter. " + DESCRIPTION;
/*
* Static Methods
*/
@NotNull
private static Pair<List<String>, String> ancestorDirectoryNamesBaseNamePair(@NotNull String moduleName) {
List<String> directoryList;
String lastAlias;
if (moduleName.contains(".")) {
String[] aliases = moduleName.split("\\.");
int directoryListSize = aliases.length - 1;
directoryList = new ArrayList<String>(directoryListSize);
for (int i = 0; i < directoryListSize; i++) {
String alias = aliases[i];
String subdirectoryName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, alias);
directoryList.add(subdirectoryName);
}
lastAlias = aliases[aliases.length - 1];
} else {
directoryList = Collections.emptyList();
lastAlias = moduleName;
}
String basename = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, lastAlias) + EXTENSION;
return pair(directoryList, basename);
}
/**
* @link com.intellij.ide.actions.CreateTemplateInPackageAction#checkOrCreate
*/
@Nullable
private static ElixirFile createDirectoryAndModuleFromTemplate(@NotNull String moduleName,
@NotNull PsiDirectory directory,
@NotNull String templateName) {
PsiDirectory currentDirectory = directory;
Pair<List<String>, String> ancestorDirectoryNamesBaseNamePair = ancestorDirectoryNamesBaseNamePair(moduleName);
List<String> ancestorDirectoryNames = ancestorDirectoryNamesBaseNamePair.first;
for (String ancestorDirectoryName : ancestorDirectoryNames) {
PsiDirectory subdirectory = currentDirectory.findSubdirectory(ancestorDirectoryName);
if (subdirectory == null) {
subdirectory = currentDirectory.createSubdirectory(ancestorDirectoryName);
}
currentDirectory = subdirectory;
}
String basename = ancestorDirectoryNamesBaseNamePair.second;
return createModuleFromTemplate(currentDirectory, basename, moduleName, templateName);
}
/**
* @link com.intellij.ide.acitons.CreateTemplateInPackageAction#doCreate
* @link com.intellij.ide.actions.CreateClassAction
* @link com.intellij.psi.impl.file.JavaDirectoryServiceImpl.createClassFromTemplate
*/
@Nullable
private static ElixirFile createModuleFromTemplate(@NotNull PsiDirectory directory,
@NotNull String basename,
@NotNull String moduleName,
@NotNull String templateName) {
FileTemplateManager fileTemplateManager = FileTemplateManager.getDefaultInstance();
FileTemplate template = fileTemplateManager.getInternalTemplate(templateName);
Properties defaultProperties = fileTemplateManager.getDefaultProperties();
Properties properties = new Properties(defaultProperties);
properties.setProperty(FileTemplate.ATTRIBUTE_NAME, moduleName);
PsiElement element;
try {
element = FileTemplateUtil.createFromTemplate(template, basename, properties, directory);
} catch (Exception exception) {
LOG.error(exception);
return null;
}
if (element == null) {
return null;
}
return (ElixirFile) element;
}
@Contract(pure = true)
@NotNull
private static String fullPath(@NotNull PsiDirectory directory,
@NotNull Pair<List<String>, String> ancestorDirectoryNamesBaseNamePair) {
return directory.getVirtualFile().getCanonicalPath() + "/" + path(ancestorDirectoryNamesBaseNamePair);
}
@Contract(pure = true)
@NotNull
private static String path(@NotNull Pair<List<String>, String> ancestorDirectoryNamesBaseNamePair) {
List<String> ancestorDirectoryNames = ancestorDirectoryNamesBaseNamePair.first;
String directoryPath = StringUtil.join(ancestorDirectoryNames, "/");
return directoryPath + ancestorDirectoryNamesBaseNamePair.second;
}
/*
* Constructors
*/
public CreateElixirModuleAction() {
super(NEW_ELIXIR_MODULE, DESCRIPTION, ElixirIcons.FILE);
}
/*
*
* Instance Methods
*
*/
/*
* Public Instance Methods
*/
@Override
public boolean equals(Object obj) {
return obj instanceof CreateElixirModuleAction;
}
@Override
public int hashCode() {
return 0;
}
/*
* Protected Instance Methods
*/
/**
* todo: the Application-template, Supervisor-template, GenServer-template, GenEvent-template should be improved
*/
@Override
protected void buildDialog(@NotNull Project project,
@NotNull final PsiDirectory directory,
@NotNull CreateFileFromTemplateDialog.Builder builder) {
builder.
setTitle(NEW_ELIXIR_MODULE).
addKind("Empty module", ElixirIcons.FILE, "Elixir Module").
addKind("Elixir Application", ElixirIcons.ELIXIR_APPLICATION, "Elixir Application").
addKind("Elixir Supervisor", ElixirIcons.ELIXIR_SUPERVISOR, "Elixir Supervisor").
addKind("Elixir GenServer", ElixirIcons.ELIXIR_GEN_SERVER, "Elixir GenServer").
addKind("Elixir GenEvent", ElixirIcons.ELIXIR_GEN_EVENT, "Elixir GenEvent").
setValidator(new InputValidatorEx() {
/*
* Public Instance Methods
*/
@Override
public boolean canClose(String inputString) {
return !StringUtil.isEmptyOrSpaces(inputString) && getErrorText(inputString) == null;
}
@Override
public boolean checkInput(String inputString) {
return checkFormat(inputString) && checkDoesNotExist(inputString);
}
@Nullable
@Override
public String getErrorText(String inputString) {
String errorText = null;
if (!StringUtil.isEmpty(inputString)) {
if (!checkFormat(inputString)) {
errorText = String.format(INVALID_MODULE_MESSAGE_FMT, inputString);
} else if (!checkDoesNotExist(inputString)) {
String fullPath = fullPath(directory, ancestorDirectoryNamesBaseNamePair(inputString));
errorText = String.format(EXISTING_MODULE_MESSAGE_FMT, fullPath);
}
}
return errorText;
}
/*
* Private Instance Methods
*/
private boolean checkDoesNotExist(@NotNull String moduleName) {
Pair<List<String>, String> ancestorDirectoryNamesBaseNamePair = ancestorDirectoryNamesBaseNamePair(
moduleName
);
List<String> ancestorDirectoryNames = ancestorDirectoryNamesBaseNamePair.first;
PsiDirectory currentDirectory = directory;
boolean doesNotExists = false;
for (String ancestorDirectoryName : ancestorDirectoryNames) {
PsiDirectory subdirectory = currentDirectory.findSubdirectory(ancestorDirectoryName);
if (subdirectory == null) {
doesNotExists = true;
break;
}
currentDirectory = subdirectory;
}
// if all the directories exist
if (!doesNotExists) {
String baseName = ancestorDirectoryNamesBaseNamePair.second;
doesNotExists = currentDirectory.findFile(baseName) == null;
}
return doesNotExists;
}
private boolean checkFormat(@NotNull String inputString) {
Matcher matcher = MODULE_NAME_PATTERN.matcher(inputString);
return matcher.matches();
}
});
}
@Override
protected String getActionName(PsiDirectory directory, String newName, String templateName) {
return NEW_ELIXIR_MODULE;
}
@Override
protected ElixirFile createFile(String name, String templateName, PsiDirectory dir) {
return createDirectoryAndModuleFromTemplate(name, dir, templateName);
}
}