package fr.adrienbrault.idea.symfony2plugin.util.psi;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import com.jetbrains.php.codeInsight.PhpCodeInsightUtil;
import com.jetbrains.php.lang.PhpFileType;
import com.jetbrains.php.lang.psi.PhpCodeEditUtil;
import com.jetbrains.php.lang.psi.PhpFile;
import com.jetbrains.php.lang.psi.PhpPsiElementFactory;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.refactoring.PhpAliasImporter;
import com.jetbrains.php.refactoring.PhpNameUtil;
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class PhpBundleFileFactory {
@Nullable
public static PsiElement invokeCreateCompilerPass(@NotNull PhpClass bundleClass, @Nullable Editor editor) {
String className = Messages.showInputDialog("Class name for CompilerPass (no namespace needed): ", "New File", Symfony2Icons.SYMFONY);
if(StringUtils.isBlank(className)) {
return null;
}
if(!PhpNameUtil.isValidClassName(className)) {
Messages.showMessageDialog(bundleClass.getProject(), "Invalid class name", "Error", Symfony2Icons.SYMFONY);
}
try {
return PhpBundleFileFactory.createCompilerPass(bundleClass, className);
} catch (Exception e) {
if(editor != null) {
HintManager.getInstance().showErrorHint(editor, "Error:" + e.getMessage());
} else {
JOptionPane.showMessageDialog(null, "Error:" + e.getMessage());
}
}
return null;
}
@NotNull
public static PsiElement createBundleFile(@NotNull PhpClass bundleClass, @NotNull String template, @NotNull String className, Map<String, String> vars) throws Exception {
VirtualFile directory = bundleClass.getContainingFile().getContainingDirectory().getVirtualFile();
if(fileExists(directory, new String[] {className})) {
throw new Exception("File already exists");
}
String COMPILER_TEMPLATE = "/resources/fileTemplates/" + template + ".php";
String fileTemplateContent = getFileTemplateContent(COMPILER_TEMPLATE);
if(fileTemplateContent == null) {
throw new Exception("Template content error");
}
String[] split = className.split("\\\\");
String ns = bundleClass.getNamespaceName();
String join = StringUtils.join(Arrays.copyOf(split, split.length - 1), "/");
vars.put("ns", (ns.startsWith("\\") ? ns.substring(1) : ns) + join.replace("/", "\\"));
vars.put("class", split[split.length - 1]);
for (Map.Entry<String, String> entry : vars.entrySet()) {
fileTemplateContent = fileTemplateContent.replace("{{ " + entry.getKey() + " }}", entry.getValue());
}
VirtualFile compilerDirectory = getAndCreateDirectory(directory, join);
if(compilerDirectory == null) {
throw new Exception("Directory creation failed");
}
Project project = bundleClass.getProject();
PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText(split[split.length - 1] + ".php", PhpFileType.INSTANCE, fileTemplateContent);
CodeStyleManager.getInstance(project).reformat(fileFromText);
return PsiDirectoryFactory.getInstance(project).createDirectory(compilerDirectory).add(fileFromText);
}
@NotNull
public static PsiElement createCompilerPass(@NotNull PhpClass bundleClass, @NotNull String className) throws Exception {
VirtualFile directory = bundleClass.getContainingFile().getContainingDirectory().getVirtualFile();
if(fileExists(directory, className)) {
throw new Exception("File already exists");
}
PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator(bundleClass);
if(scopeForUseOperator == null) {
throw new Exception("No 'use' scope found");
}
VirtualFile compilerDirectory = getAndCreateCompilerDirectory(directory);
if(compilerDirectory == null) {
throw new Exception("Directory creation failed");
}
Project project = bundleClass.getProject();
if(bundleClass.findOwnMethodByName("build") == null) {
insertUseIfNecessary(scopeForUseOperator, "\\Symfony\\Component\\DependencyInjection\\ContainerBuilder");
Method method = PhpPsiElementFactory.createMethod(project, "" +
"public function build(ContainerBuilder $container)\n" +
" {\n" +
" parent::build($container);\n" +
" }"
);
PhpCodeEditUtil.insertClassMember(bundleClass, method);
}
Method buildMethod = bundleClass.findOwnMethodByName("build");
if(buildMethod == null) {
throw new Exception("No 'build' method found");
}
String relativePath = VfsUtil.getRelativePath(compilerDirectory, directory);
if(relativePath == null) {
throw new Exception("path error");
}
MethodReference methodReference = PhpPsiElementFactory.createMethodReference(project, "$container->addCompilerPass(new " + className + "());");
String ns = bundleClass.getNamespaceName() + relativePath.replace("/", "\\");
String nsClass = ns + "\\" + className;
insertUseIfNecessary(scopeForUseOperator, nsClass);
GroupStatement groupStatement = PhpPsiUtil.getChildByCondition(buildMethod, GroupStatement.INSTANCEOF);
if(groupStatement != null) {
PsiElement semicolon = methodReference.getNextSibling();
groupStatement.addRangeBefore(methodReference, semicolon, groupStatement.getLastChild());
}
String COMPILER_TEMPLATE = "/resources/fileTemplates/compiler_pass.php";
String fileTemplateContent = getFileTemplateContent(COMPILER_TEMPLATE);
if(fileTemplateContent == null) {
throw new Exception("Template content error");
}
String replace = fileTemplateContent.replace("{{ ns }}", ns.startsWith("\\") ? ns.substring(1) : ns).replace("{{ class }}", className);
PsiFile fileFromText = PsiFileFactory.getInstance(project).createFileFromText(className + ".php", PhpFileType.INSTANCE, replace);
CodeStyleManager.getInstance(project).reformat(fileFromText);
return PsiDirectoryFactory.getInstance(project).createDirectory(compilerDirectory).add(fileFromText);
}
@Nullable
public static PhpClass getPhpClassForCreateCompilerScope(@Nullable PhpClass phpClass) {
if(phpClass == null) {
return null;
}
if(!PhpElementsUtil.isInstanceOf(phpClass, "\\Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface")) {
return null;
}
return phpClass;
}
@Nullable
public static PhpClass getPhpClassForCreateCompilerScope(@NotNull Editor editor, @Nullable PsiFile file) {
if(file == null || !(file instanceof PhpFile)) {
return null;
}
return getPhpClassForCreateCompilerScope(PhpCodeEditUtil.findClassAtCaret(editor, file));
}
private static void insertUseIfNecessary(PhpPsiElement scopeForUseOperator, String nsClass) {
if(!PhpCodeInsightUtil.getAliasesInScope(scopeForUseOperator).values().contains(nsClass)) {
PhpAliasImporter.insertUseStatement(nsClass, scopeForUseOperator);
}
}
private static boolean fileExists(@NotNull VirtualFile bundleDir, @NotNull String className) {
return
VfsUtil.findRelativeFile(bundleDir, "DependencyInjection", "Compiler", className + ".php") != null ||
VfsUtil.findRelativeFile(bundleDir, "DependencyInjection", "CompilerPass", className + ".php") != null;
}
private static boolean fileExists(@NotNull VirtualFile bundleDir, @NotNull String... fqnClassName) {
for (String s : fqnClassName) {
String[] split = s.split("/");
split[split.length - 1] += ".php";
if(VfsUtil.findRelativeFile(bundleDir, split) != null) {
return true;
}
}
return false;
}
@Nullable
private static VirtualFile getAndCreateDirectory(@NotNull VirtualFile directory, @NotNull String relativePath) {
try {
return VfsUtil.createDirectoryIfMissing(directory, relativePath);
} catch (IOException ignored) {
}
return null;
}
@Nullable
private static VirtualFile getAndCreateCompilerDirectory(@NotNull VirtualFile directory) {
VirtualFile relativeFile = VfsUtil.findRelativeFile(directory, "DependencyInjection", "Compiler");
if(relativeFile != null) {
return relativeFile;
}
relativeFile = VfsUtil.findRelativeFile(directory, "DependencyInjection", "CompilerPass");
if(relativeFile != null) {
return relativeFile;
}
try {
return VfsUtil.createDirectoryIfMissing(directory, "DependencyInjection/Compiler");
} catch (IOException ignored) {
}
return null;
}
@Nullable
private static String getFileTemplateContent(@NotNull String filename) {
try {
// replace on windows, just for secure reasons
return StreamUtil.readText(PhpBundleFileFactory.class.getResourceAsStream(filename), "UTF-8").replace("\r\n", "\n");
} catch (IOException e) {
return null;
}
}
}