com.intellij.refactoring.lang.ExtractIncludeFileBase.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.refactoring.lang.ExtractIncludeFileBase.java

Source

/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * 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 com.intellij.refactoring.lang;

import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.find.FindManager;
import com.intellij.ide.TitledHandler;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileSystemItemUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.ReplacePromptDialog;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PairConsumer;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
 * @author ven
 */
public abstract class ExtractIncludeFileBase<T extends PsiElement>
        implements RefactoringActionHandler, TitledHandler {
    private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.lang.ExtractIncludeFileBase");
    private static final String REFACTORING_NAME = RefactoringBundle.message("extract.include.file.title");
    protected PsiFile myIncludingFile;
    public static final String HELP_ID = "refactoring.extractInclude";

    private static class IncludeDuplicate<E extends PsiElement> {
        private final SmartPsiElementPointer<E> myStart;
        private final SmartPsiElementPointer<E> myEnd;

        private IncludeDuplicate(E start, E end) {
            myStart = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(start);
            myEnd = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(end);
        }

        E getStart() {
            return myStart.getElement();
        }

        E getEnd() {
            return myEnd.getElement();
        }
    }

    protected abstract void doReplaceRange(final String includePath, final T first, final T last);

    @NotNull
    protected String doExtract(final PsiDirectory targetDirectory, final String targetfileName, final T first,
            final T last, final Language includingLanguage) throws IncorrectOperationException {
        final PsiFile file = targetDirectory.createFile(targetfileName);
        Project project = targetDirectory.getProject();
        final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
        final Document document = documentManager.getDocument(file);
        document.replaceString(0, document.getTextLength(), first.getText().trim());
        documentManager.commitDocument(document);
        CodeStyleManager.getInstance(PsiManager.getInstance(project).getProject()).reformat(file); //TODO: adjustLineIndent

        final String relativePath = PsiFileSystemItemUtil.getRelativePath(first.getContainingFile(), file);
        if (relativePath == null)
            throw new IncorrectOperationException("Cannot extract!");
        return relativePath;
    }

    protected abstract boolean verifyChildRange(final T first, final T last);

    private void replaceDuplicates(final String includePath, final List<IncludeDuplicate<T>> duplicates,
            final Editor editor, final Project project) {
        if (duplicates.size() > 0) {
            final String message = RefactoringBundle.message(
                    "idea.has.found.fragments.that.can.be.replaced.with.include.directive",
                    ApplicationNamesInfo.getInstance().getProductName());
            final int exitCode = Messages.showYesNoDialog(project, message, getRefactoringName(),
                    Messages.getInformationIcon());
            if (exitCode == Messages.YES) {
                CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                    @Override
                    public void run() {
                        boolean replaceAll = false;
                        for (IncludeDuplicate<T> pair : duplicates) {
                            if (!replaceAll) {

                                highlightInEditor(project, pair, editor);

                                ReplacePromptDialog promptDialog = new ReplacePromptDialog(false,
                                        RefactoringBundle.message("replace.fragment"), project);
                                promptDialog.show();
                                final int promptResult = promptDialog.getExitCode();
                                if (promptResult == FindManager.PromptResult.SKIP)
                                    continue;
                                if (promptResult == FindManager.PromptResult.CANCEL)
                                    break;

                                if (promptResult == FindManager.PromptResult.OK) {
                                    doReplaceRange(includePath, pair.getStart(), pair.getEnd());
                                } else if (promptResult == FindManager.PromptResult.ALL) {
                                    doReplaceRange(includePath, pair.getStart(), pair.getEnd());
                                    replaceAll = true;
                                } else {
                                    LOG.error("Unknown return status");
                                }
                            } else {
                                doReplaceRange(includePath, pair.getStart(), pair.getEnd());
                            }
                        }
                    }
                }, RefactoringBundle.message("remove.duplicates.command"), null);
            }
        }
    }

    private static void highlightInEditor(final Project project, final IncludeDuplicate pair, final Editor editor) {
        final HighlightManager highlightManager = HighlightManager.getInstance(project);
        EditorColorsManager colorsManager = EditorColorsManager.getInstance();
        TextAttributes attributes = colorsManager.getGlobalScheme()
                .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
        final int startOffset = pair.getStart().getTextRange().getStartOffset();
        final int endOffset = pair.getEnd().getTextRange().getEndOffset();
        highlightManager.addRangeHighlight(editor, startOffset, endOffset, attributes, true, null);
        final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(startOffset);
        editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE);
    }

    @Override
    public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
    }

    @NotNull
    protected Language getLanguageForExtract(PsiElement firstExtracted) {
        return firstExtracted.getLanguage();
    }

    @Nullable
    private static FileType getFileType(final Language language) {
        final FileType[] fileTypes = FileTypeManager.getInstance().getRegisteredFileTypes();
        for (FileType fileType : fileTypes) {
            if (fileType instanceof LanguageFileType
                    && language.equals(((LanguageFileType) fileType).getLanguage()))
                return fileType;
        }

        return null;
    }

    @Override
    public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file,
            DataContext dataContext) {
        myIncludingFile = file;
        if (!editor.getSelectionModel().hasSelection()) {
            String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("no.selection"));
            CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
            return;
        }
        final int start = editor.getSelectionModel().getSelectionStart();
        final int end = editor.getSelectionModel().getSelectionEnd();

        final Pair<T, T> children = findPairToExtract(start, end);
        if (children == null) {
            String message = RefactoringBundle.getCannotRefactorMessage(
                    RefactoringBundle.message("selection.does.not.form.a.fragment.for.extraction"));
            CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
            return;
        }

        if (!verifyChildRange(children.getFirst(), children.getSecond())) {
            String message = RefactoringBundle.getCannotRefactorMessage(
                    RefactoringBundle.message("cannot.extract.selected.elements.into.include.file"));
            CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
            return;
        }

        final FileType fileType = getFileType(getLanguageForExtract(children.getFirst()));
        if (!(fileType instanceof LanguageFileType)) {
            String message = RefactoringBundle
                    .message("the.language.for.selected.elements.has.no.associated.file.type");
            CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
            return;
        }

        if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file))
            return;

        ExtractIncludeDialog dialog = createDialog(file.getContainingDirectory(),
                getExtractExtension(fileType, children.first));
        dialog.show();
        if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
            final PsiDirectory targetDirectory = dialog.getTargetDirectory();
            LOG.assertTrue(targetDirectory != null);
            final String targetfileName = dialog.getTargetFileName();
            CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                @Override
                public void run() {
                    ApplicationManager.getApplication().runWriteAction(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                final List<IncludeDuplicate<T>> duplicates = new ArrayList<IncludeDuplicate<T>>();
                                final T first = children.getFirst();
                                final T second = children.getSecond();
                                PsiEquivalenceUtil.findChildRangeDuplicates(first, second, file,
                                        new PairConsumer<PsiElement, PsiElement>() {
                                            @Override
                                            public void consume(final PsiElement start, final PsiElement end) {
                                                duplicates.add(new IncludeDuplicate<T>((T) start, (T) end));
                                            }
                                        });
                                final String includePath = processPrimaryFragment(first, second, targetDirectory,
                                        targetfileName, file);
                                editor.getCaretModel().moveToOffset(first.getTextRange().getStartOffset());

                                ApplicationManager.getApplication().invokeLater(new Runnable() {
                                    @Override
                                    public void run() {
                                        replaceDuplicates(includePath, duplicates, editor, project);
                                    }
                                });
                            } catch (IncorrectOperationException e) {
                                CommonRefactoringUtil.showErrorMessage(getRefactoringName(), e.getMessage(), null,
                                        project);
                            }

                            editor.getSelectionModel().removeSelection();
                        }
                    });
                }
            }, getRefactoringName(), null);

        }
    }

    protected ExtractIncludeDialog createDialog(final PsiDirectory containingDirectory,
            final String extractExtension) {
        return new ExtractIncludeDialog(containingDirectory, extractExtension);
    }

    @Nullable
    protected abstract Pair<T, T> findPairToExtract(int start, int end);

    @NonNls
    protected String getExtractExtension(final FileType extractFileType, final T first) {
        return extractFileType.getDefaultExtension();
    }

    public boolean isValidRange(final T firstToExtract, final T lastToExtract) {
        return verifyChildRange(firstToExtract, lastToExtract);
    }

    public String processPrimaryFragment(final T firstToExtract, final T lastToExtract,
            final PsiDirectory targetDirectory, final String targetfileName, final PsiFile srcFile)
            throws IncorrectOperationException {
        final String includePath = doExtract(targetDirectory, targetfileName, firstToExtract, lastToExtract,
                srcFile.getLanguage());

        doReplaceRange(includePath, firstToExtract, lastToExtract);
        return includePath;
    }

    @Override
    public String getActionTitle() {
        return "Extract Include File...";
    }

    protected String getRefactoringName() {
        return REFACTORING_NAME;
    }
}