/*
* Copyright 2011-2014 Gregory Shrago
*
* 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.intellij.grammar.actions;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind;
import com.intellij.codeInsight.intention.impl.CreateClassDialog;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.generator.BnfConstants;
import org.intellij.grammar.psi.BnfAttr;
import org.intellij.grammar.psi.BnfAttrs;
import org.intellij.grammar.psi.BnfFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author greg
*/
public class BnfGenerateParserUtilAction extends AnAction {
@Override
public void update(AnActionEvent e) {
PsiFile file = LangDataKeys.PSI_FILE.getData(e.getDataContext());
if (!(file instanceof BnfFile)) {
e.getPresentation().setEnabledAndVisible(false);
}
else {
boolean enabled = ((BnfFile) file).findAttribute(null, KnownAttribute.PARSER_UTIL_CLASS, null) == null;
e.getPresentation().setEnabledAndVisible(enabled);
}
}
@Override
public void actionPerformed(AnActionEvent e) {
PsiFile file = LangDataKeys.PSI_FILE.getData(e.getDataContext());
if (!(file instanceof BnfFile)) return;
Project project = file.getProject();
BnfFile bnfFile = (BnfFile) file;
final String qualifiedName = createClass(
bnfFile, "Create Parser Util Class", BnfConstants.GPUB_CLASS,
getGrammarName(bnfFile) + "ParserUtil",
getGrammarPackage(bnfFile));
if (qualifiedName == null) return;
final int anchorOffset;
final String text;
String definition = "\n " + KnownAttribute.PARSER_UTIL_CLASS.getName() + "=\"" + qualifiedName + "\"";
BnfAttr attrParser = bnfFile.findAttribute(null, KnownAttribute.PARSER_CLASS, null);
if (attrParser == null) {
BnfAttrs rootAttrs = ContainerUtil.getFirstItem(bnfFile.getAttributes());
if (rootAttrs == null) {
anchorOffset = 0;
text = "{" + definition + "\n}";
}
else {
anchorOffset = rootAttrs.getFirstChild().getTextOffset();
text = definition;
}
}
else {
anchorOffset = attrParser.getTextRange().getEndOffset();
text = definition;
}
final Document document = PsiDocumentManager.getInstance(project).getDocument(bnfFile);
if (document == null) return;
new WriteCommandAction.Simple(project, file) {
@Override
protected void run() throws Throwable {
int position = document.getLineEndOffset(document.getLineNumber(anchorOffset));
document.insertString(position, text);
}
}.execute();
}
static String getGrammarPackage(BnfFile bnfFile) {
return StringUtil.getPackageName(bnfFile.findAttributeValue(null, KnownAttribute.PARSER_CLASS, null));
}
static String getGrammarName(BnfFile bnfFile) {
String parser = bnfFile.findAttributeValue(null, KnownAttribute.PARSER_CLASS, null);
if (!KnownAttribute.PARSER_CLASS.getDefaultValue().equals(parser)) {
String shortName = StringUtil.getShortName(parser);
int len = "Parser".length();
String result = shortName.endsWith("Parser") ? shortName.substring(0, shortName.length() - len) : shortName;
if (StringUtil.isNotEmpty(result)) return result;
}
return StringUtil.capitalize(FileUtil.getNameWithoutExtension(bnfFile.getName()));
}
public static String createClass(@NotNull PsiFile origin,
@NotNull final String title,
@Nullable final String baseClass,
@NotNull String suggestedName,
@NotNull String suggestedPackage) {
Project project = origin.getProject();
Module module = ModuleUtilCore.findModuleForPsiElement(origin);
CreateClassDialog dialog = new CreateClassDialog(project, title, suggestedName, suggestedPackage, CreateClassKind.CLASS, true, module);
if (!dialog.showAndGet()) return null;
final String className = dialog.getClassName();
final PsiDirectory targetDirectory = dialog.getTargetDirectory();
return createClass(className, targetDirectory, baseClass, title, null);
}
static String createClass(final String className, final PsiDirectory targetDirectory,
final String baseClass,
final String title,
final Consumer<PsiClass> consumer) {
final Project project = targetDirectory.getProject();
final Ref<PsiClass> resultRef = Ref.create();
new WriteCommandAction(project, title) {
@Override
protected void run(Result result) throws Throwable {
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiJavaCodeReferenceElement ref = baseClass == null ? null : elementFactory.createReferenceElementByFQClassName(
baseClass, GlobalSearchScope.allScope(project));
try {
PsiClass resultClass = JavaDirectoryService.getInstance().createClass(targetDirectory, className);
resultRef.set(resultClass);
if (ref != null) {
PsiElement baseClass = ref.resolve();
boolean isInterface = baseClass instanceof PsiClass && ((PsiClass) baseClass).isInterface();
PsiReferenceList targetReferenceList = isInterface ? resultClass.getImplementsList() : resultClass.getExtendsList();
assert targetReferenceList != null;
targetReferenceList.add(ref);
}
if (consumer != null) {
consumer.consume(resultClass);
}
}
catch (final IncorrectOperationException e) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Messages.showErrorDialog(project, "Unable to create class " + className + "\n" + e.getLocalizedMessage(), title);
}
});
}
}
}.execute();
return resultRef.isNull() ? null : resultRef.get().getQualifiedName();
}
}