package com.github.setial.intellijjavadocs.operation.impl; import com.github.setial.intellijjavadocs.exception.FileNotValidException; import com.github.setial.intellijjavadocs.exception.NotFoundElementException; import com.github.setial.intellijjavadocs.operation.JavaDocWriter; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.RunResult; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.ReadonlyStatusHandler.OperationStatus; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.util.PsiUtilBase; import org.jetbrains.annotations.NotNull; import java.util.Collections; /** * The type Java doc writer impl. * * @author Sergey Timofiychuk */ public class JavaDocWriterImpl implements JavaDocWriter { /** * The constant COMPONENT_NAME. */ public static final String COMPONENT_NAME = "JavaDocWriter"; private static final Logger LOGGER = Logger.getInstance(JavaDocWriterImpl.class); @Override public void initComponent() { } @Override public void disposeComponent() { } @NotNull @Override public String getComponentName() { return COMPONENT_NAME; } @Override public void write(@NotNull PsiDocComment javaDoc, @NotNull PsiElement beforeElement) { try { checkFilesAccess(beforeElement); } catch (FileNotValidException e) { LOGGER.error(e.getMessage()); Messages.showErrorDialog("Javadocs plugin is not available, cause: " + e.getMessage(), "Javadocs plugin"); return; } WriteCommandAction command = new WriteJavaDocActionImpl(javaDoc, beforeElement); RunResult result = command.execute(); if (result.hasException()) { LOGGER.error(result.getThrowable()); Messages.showErrorDialog("Javadocs plugin is not available, cause: " + result.getThrowable().getMessage(), "Javadocs plugin"); } } @Override public void remove(@NotNull PsiElement beforeElement) { try { checkFilesAccess(beforeElement); } catch (FileNotValidException e) { LOGGER.error(e); Messages.showErrorDialog("Javadocs plugin is not available, cause: " + e.getMessage(), "Javadocs plugin"); return; } WriteCommandAction command = new RemoveJavaDocActionImpl(beforeElement); RunResult result = command.execute(); if (result.hasException()) { LOGGER.error(result.getThrowable()); Messages.showErrorDialog("Javadocs plugin is not available, cause: " + result.getThrowable().getMessage(), "Javadocs plugin"); } } /** * The type Write command action impl. * * @author Sergey Timofiychuk */ private static class WriteJavaDocActionImpl extends WriteCommandAction { private PsiDocComment javaDoc; private PsiElement element; /** * Instantiates a new Write java doc action impl. * * @param javaDoc the java doc * @param element the element */ protected WriteJavaDocActionImpl(@NotNull PsiDocComment javaDoc, @NotNull PsiElement element) { super( element.getProject(), WRITE_JAVADOC_COMMAND_NAME, WRITE_JAVADOC_COMMAND_GROUP, element.getContainingFile()); this.javaDoc = javaDoc; this.element = element; } @Override protected void run(@NotNull Result result) throws Throwable { if (javaDoc == null) { return; } if (element.getFirstChild() instanceof PsiDocComment) { replaceJavaDoc(element, javaDoc); } else { addJavaDoc(element, javaDoc); } ensureWhitespaceAfterJavaDoc(element); reformatJavaDoc(element); } private void ensureWhitespaceAfterJavaDoc(PsiElement element) { // this method is required to create well formatted javadocs in enums PsiElement firstChild = element.getFirstChild(); if (!PsiDocComment.class.isAssignableFrom(firstChild.getClass())) { return; } PsiElement nextElement = firstChild.getNextSibling(); if (PsiWhiteSpace.class.isAssignableFrom(nextElement.getClass())) { return; } pushPostponedChanges(element); element.getNode().addChild(new PsiWhiteSpaceImpl("\n"), nextElement.getNode()); } } private static class RemoveJavaDocActionImpl extends WriteCommandAction { private PsiElement element; /** * Instantiates a new Remove java doc action impl. * * @param element the element */ protected RemoveJavaDocActionImpl(PsiElement element) { super( element.getProject(), WRITE_JAVADOC_COMMAND_NAME, WRITE_JAVADOC_COMMAND_GROUP, element.getContainingFile()); this.element = element; } @Override protected void run(@NotNull Result result) throws Throwable { if (element.getFirstChild() instanceof PsiDocComment) { deleteJavaDoc(this.element); } } } private static void deleteJavaDoc(PsiElement theElement) { pushPostponedChanges(theElement); theElement.getFirstChild().delete(); } private static void addJavaDoc(PsiElement theElement, PsiDocComment theJavaDoc) { pushPostponedChanges(theElement); theElement.getNode().addChild(theJavaDoc.getNode(), theElement.getFirstChild().getNode()); } private static void replaceJavaDoc(PsiElement theElement, PsiDocComment theJavaDoc) { deleteJavaDoc(theElement); addJavaDoc(theElement, theJavaDoc); } private static void reformatJavaDoc(PsiElement theElement) { CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(theElement.getProject()); pushPostponedChanges(theElement); try { int javadocTextOffset = findJavaDocTextOffset(theElement); int javaCodeTextOffset = findJavaCodeTextOffset(theElement); codeStyleManager.reformatText(theElement.getContainingFile(), javadocTextOffset, javaCodeTextOffset + 1); } catch (NotFoundElementException e) { LOGGER.info("Could not reformat javadoc since cannot find required elements", e); } } private static int findJavaDocTextOffset(PsiElement theElement) { PsiElement javadocElement = theElement.getFirstChild(); if (!(javadocElement instanceof PsiDocComment)) { throw new NotFoundElementException("Cannot find element of type PsiDocComment"); } return javadocElement.getTextOffset(); } private static int findJavaCodeTextOffset(PsiElement theElement) { if (theElement.getChildren().length < 2) { throw new NotFoundElementException("Can not find offset of java code"); } return theElement.getChildren()[1].getTextOffset(); } private static void pushPostponedChanges(PsiElement element) { Editor editor = PsiUtilBase.findEditor(element.getContainingFile()); if (editor != null) { PsiDocumentManager.getInstance(element.getProject()) .doPostponedOperationsAndUnblockDocument(editor.getDocument()); } } private void checkFilesAccess(@NotNull PsiElement beforeElement) { PsiFile containingFile = beforeElement.getContainingFile(); if (containingFile == null || !containingFile.isValid()) { throw new FileNotValidException("File cannot be used to generate javadocs"); } OperationStatus status = ReadonlyStatusHandler.getInstance(beforeElement.getProject()). ensureFilesWritable(Collections.singletonList(containingFile.getVirtualFile())); if (status.hasReadonlyFiles()) { throw new FileNotValidException(status.getReadonlyFilesMessage()); } } }