com.intellij.conversion.impl.ConversionServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.conversion.impl.ConversionServiceImpl.java

Source

/*
 * Copyright 2000-2012 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.conversion.impl;

import com.intellij.conversion.*;
import com.intellij.conversion.impl.ui.ConvertProjectDialog;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.impl.stores.IProjectStore;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.impl.ProjectImpl;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemProperties;
import com.intellij.util.graph.CachingSemiGraph;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.AbstractCollection;
import com.intellij.util.xmlb.annotations.MapAnnotation;
import com.intellij.util.xmlb.annotations.Tag;
import org.jdom.Document;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * @author nik
 */
public class ConversionServiceImpl extends ConversionService {
    private static final Logger LOG = Logger.getInstance("#com.intellij.conversion.impl.ConversionServiceImpl");

    @NotNull
    @Override
    public ConversionResult convertSilently(@NotNull String projectPath) {
        return convertSilently(projectPath, new ConversionListener() {
            @Override
            public void conversionNeeded() {
            }

            @Override
            public void successfullyConverted(File backupDir) {
            }

            @Override
            public void error(String message) {
            }

            @Override
            public void cannotWriteToFiles(List<File> readonlyFiles) {
            }
        });
    }

    @NotNull
    @Override
    public ConversionResult convertSilently(@NotNull String projectPath, @NotNull ConversionListener listener) {
        try {
            if (!isConversionNeeded(projectPath)) {
                return ConversionResultImpl.CONVERSION_NOT_NEEDED;
            }

            listener.conversionNeeded();
            ConversionContextImpl context = new ConversionContextImpl(projectPath);
            final List<ConversionRunner> runners = getConversionRunners(context);

            Set<File> affectedFiles = new HashSet<File>();
            for (ConversionRunner runner : runners) {
                affectedFiles.addAll(runner.getAffectedFiles());
            }

            final List<File> readOnlyFiles = ConversionRunner.getReadOnlyFiles(affectedFiles);
            if (!readOnlyFiles.isEmpty()) {
                listener.cannotWriteToFiles(readOnlyFiles);
                return ConversionResultImpl.ERROR_OCCURRED;
            }
            final File backupDir = ProjectConversionUtil.backupFiles(affectedFiles, context.getProjectBaseDir());
            List<ConversionRunner> usedRunners = new ArrayList<ConversionRunner>();
            for (ConversionRunner runner : runners) {
                if (runner.isConversionNeeded()) {
                    runner.preProcess();
                    runner.process();
                    runner.postProcess();
                    usedRunners.add(runner);
                }
            }
            context.saveFiles(affectedFiles, usedRunners);
            listener.successfullyConverted(backupDir);
            saveConversionResult(context);
            return new ConversionResultImpl(runners);
        } catch (CannotConvertException e) {
            listener.error(e.getMessage());
        } catch (IOException e) {
            listener.error(e.getMessage());
        }
        return ConversionResultImpl.ERROR_OCCURRED;
    }

    @NotNull
    @Override
    public ConversionResult convert(@NotNull String projectPath) {
        try {
            if (!new File(projectPath).exists() || ApplicationManager.getApplication().isHeadlessEnvironment()
                    || !isConversionNeeded(projectPath)) {
                return ConversionResultImpl.CONVERSION_NOT_NEEDED;
            }

            final ConversionContextImpl context = new ConversionContextImpl(projectPath);
            final List<ConversionRunner> converters = getConversionRunners(context);
            ConvertProjectDialog dialog = new ConvertProjectDialog(context, converters);
            dialog.show();
            if (dialog.isConverted()) {
                saveConversionResult(context);
                return new ConversionResultImpl(converters);
            }
            return ConversionResultImpl.CONVERSION_CANCELED;
        } catch (CannotConvertException e) {
            LOG.info(e);
            Messages.showErrorDialog(IdeBundle.message("error.cannot.convert.project", e.getMessage()),
                    IdeBundle.message("title.cannot.convert.project"));
            return ConversionResultImpl.ERROR_OCCURRED;
        }
    }

    private static List<ConversionRunner> getConversionRunners(ConversionContextImpl context)
            throws CannotConvertException {
        final List<ConversionRunner> converters = getSortedConverters(context);
        final Iterator<ConversionRunner> iterator = converters.iterator();

        Set<String> convertersToRunIds = new HashSet<String>();
        while (iterator.hasNext()) {
            ConversionRunner runner = iterator.next();
            boolean conversionNeeded = runner.isConversionNeeded();
            if (!conversionNeeded) {
                for (String id : runner.getProvider().getPrecedingConverterIds()) {
                    if (convertersToRunIds.contains(id)) {
                        conversionNeeded = true;
                        break;
                    }
                }
            }

            if (conversionNeeded) {
                convertersToRunIds.add(runner.getProvider().getId());
            } else {
                iterator.remove();
            }
        }
        return converters;
    }

    public static boolean isConversionNeeded(String projectPath) throws CannotConvertException {
        final ConversionContextImpl context = new ConversionContextImpl(projectPath);
        final List<ConversionRunner> runners = getSortedConverters(context);
        if (runners.isEmpty()) {
            return false;
        }
        for (ConversionRunner runner : runners) {
            if (runner.isConversionNeeded()) {
                return true;
            }
        }
        saveConversionResult(context);
        return false;
    }

    private static List<ConversionRunner> getSortedConverters(final ConversionContextImpl context)
            throws CannotConvertException {
        final CachedConversionResult conversionResult = loadCachedConversionResult(context.getProjectFile());
        final Map<String, Long> oldMap = conversionResult.myProjectFilesTimestamps;
        Map<String, Long> newMap = getProjectFilesMap(context);
        boolean changed = false;
        LOG.debug("Checking project files");
        for (Map.Entry<String, Long> entry : newMap.entrySet()) {
            final String path = entry.getKey();
            final Long oldValue = oldMap.get(path);
            if (oldValue == null) {
                LOG.debug(" new file: " + path);
                changed = true;
            } else if (!entry.getValue().equals(oldValue)) {
                LOG.debug(" changed file: " + path);
                changed = true;
            }
        }

        final Set<String> performedConversionIds;
        if (changed) {
            performedConversionIds = Collections.emptySet();
            LOG.debug("Project files were modified.");
        } else {
            performedConversionIds = conversionResult.myAppliedConverters;
            LOG.debug("Project files are up to date. Applied converters: " + performedConversionIds);
        }
        return createConversionRunners(context, performedConversionIds);
    }

    private static Map<String, Long> getProjectFilesMap(ConversionContextImpl context) {
        final Map<String, Long> map = new HashMap<String, Long>();
        for (File file : context.getAllProjectFiles()) {
            if (file.exists()) {
                map.put(file.getAbsolutePath(), file.lastModified());
            }
        }
        return map;
    }

    private static List<ConversionRunner> createConversionRunners(ConversionContextImpl context,
            final Set<String> performedConversionIds) {
        List<ConversionRunner> runners = new ArrayList<ConversionRunner>();
        final ConverterProvider[] providers = ConverterProvider.EP_NAME.getExtensions();
        for (ConverterProvider provider : providers) {
            if (!performedConversionIds.contains(provider.getId())) {
                runners.add(new ConversionRunner(provider, context));
            }
        }
        final CachingSemiGraph<ConverterProvider> graph = CachingSemiGraph
                .create(new ConverterProvidersGraph(providers));
        final DFSTBuilder<ConverterProvider> builder = new DFSTBuilder<ConverterProvider>(
                GraphGenerator.create(graph));
        if (!builder.isAcyclic()) {
            final Pair<ConverterProvider, ConverterProvider> pair = builder.getCircularDependency();
            LOG.error("cyclic dependencies between converters: " + pair.getFirst().getId() + " and "
                    + pair.getSecond().getId());
        }
        final Comparator<ConverterProvider> comparator = builder.comparator();
        Collections.sort(runners, new Comparator<ConversionRunner>() {
            @Override
            public int compare(ConversionRunner o1, ConversionRunner o2) {
                return comparator.compare(o1.getProvider(), o2.getProvider());
            }
        });
        return runners;
    }

    public static void saveConversionResult(String projectPath) {
        try {
            saveConversionResult(new ConversionContextImpl(projectPath));
        } catch (CannotConvertException e) {
            LOG.info(e);
        }
    }

    private static void saveConversionResult(ConversionContextImpl context) {
        final CachedConversionResult conversionResult = new CachedConversionResult();
        for (ConverterProvider provider : ConverterProvider.EP_NAME.getExtensions()) {
            conversionResult.myAppliedConverters.add(provider.getId());
        }
        conversionResult.myProjectFilesTimestamps = getProjectFilesMap(context);
        final File infoFile = getConversionInfoFile(context.getProjectFile());
        FileUtil.createParentDirs(infoFile);
        try {
            JDOMUtil.writeDocument(new Document(XmlSerializer.serialize(conversionResult)), infoFile,
                    SystemProperties.getLineSeparator());
        } catch (IOException e) {
            LOG.info(e);
        }
    }

    @NotNull
    private static CachedConversionResult loadCachedConversionResult(File projectFile) {
        try {
            final File infoFile = getConversionInfoFile(projectFile);
            if (!infoFile.exists()) {
                return new CachedConversionResult();
            }
            final Document document = JDOMUtil.loadDocument(infoFile);
            final CachedConversionResult result = XmlSerializer.deserialize(document, CachedConversionResult.class);
            return result != null ? result : new CachedConversionResult();
        } catch (Exception e) {
            LOG.info(e);
            return new CachedConversionResult();
        }
    }

    private static File getConversionInfoFile(@NotNull File projectFile) {
        String dirName = PathUtil.suggestFileName(
                projectFile.getName() + Integer.toHexString(projectFile.getAbsolutePath().hashCode()));
        return new File(
                PathManager.getSystemPath() + File.separator + "conversion" + File.separator + dirName + ".xml");
    }

    @Override
    @NotNull
    public ConversionResult convertModule(@NotNull final Project project, @NotNull final File moduleFile) {
        final IProjectStore stateStore = ((ProjectImpl) project).getStateStore();
        final String url = stateStore.getPresentableUrl();
        assert url != null : project;
        final String projectPath = FileUtil.toSystemDependentName(url);

        if (!isConversionNeeded(projectPath, moduleFile)) {
            return ConversionResultImpl.CONVERSION_NOT_NEEDED;
        }

        final int res = Messages.showYesNoDialog(project,
                IdeBundle.message("message.module.file.has.an.older.format.do.you.want.to.convert.it"),
                IdeBundle.message("dialog.title.convert.module"), Messages.getQuestionIcon());
        if (res != Messages.YES) {
            return ConversionResultImpl.CONVERSION_CANCELED;
        }
        if (!moduleFile.canWrite()) {
            Messages.showErrorDialog(project,
                    IdeBundle.message("error.message.cannot.modify.file.0", moduleFile.getAbsolutePath()),
                    IdeBundle.message("dialog.title.convert.module"));
            return ConversionResultImpl.ERROR_OCCURRED;
        }

        try {
            ConversionContextImpl context = new ConversionContextImpl(projectPath);
            final List<ConversionRunner> runners = createConversionRunners(context, Collections.<String>emptySet());
            final File backupFile = ProjectConversionUtil.backupFile(moduleFile);
            List<ConversionRunner> usedRunners = new ArrayList<ConversionRunner>();
            for (ConversionRunner runner : runners) {
                if (runner.isModuleConversionNeeded(moduleFile)) {
                    runner.convertModule(moduleFile);
                    usedRunners.add(runner);
                }
            }
            context.saveFiles(Collections.singletonList(moduleFile), usedRunners);
            Messages.showInfoMessage(project,
                    IdeBundle.message(
                            "message.your.module.was.successfully.converted.br.old.version.was.saved.to.0",
                            backupFile.getAbsolutePath()),
                    IdeBundle.message("dialog.title.convert.module"));
            return new ConversionResultImpl(runners);
        } catch (CannotConvertException e) {
            LOG.info(e);
            Messages.showErrorDialog(IdeBundle.message("error.cannot.load.project", e.getMessage()),
                    "Cannot Convert Module");
            return ConversionResultImpl.ERROR_OCCURRED;
        } catch (IOException e) {
            LOG.info(e);
            return ConversionResultImpl.ERROR_OCCURRED;
        }
    }

    private static boolean isConversionNeeded(String projectPath, File moduleFile) {
        try {
            ConversionContextImpl context = new ConversionContextImpl(projectPath);
            final List<ConversionRunner> runners = createConversionRunners(context, Collections.<String>emptySet());
            for (ConversionRunner runner : runners) {
                if (runner.isModuleConversionNeeded(moduleFile)) {
                    return true;
                }
            }
            return false;
        } catch (CannotConvertException e) {
            LOG.info(e);
            return false;
        }
    }

    @Tag("conversion")
    public static class CachedConversionResult {
        @Tag("applied-converters")
        @AbstractCollection(surroundWithTag = false, elementTag = "converter", elementValueAttribute = "id")
        public Set<String> myAppliedConverters = new HashSet<String>();

        @Tag("project-files")
        @MapAnnotation(surroundWithTag = false, surroundKeyWithTag = false, surroundValueWithTag = false, entryTagName = "file", keyAttributeName = "path", valueAttributeName = "timestamp")
        public Map<String, Long> myProjectFilesTimestamps = new HashMap<String, Long>();
    }

    private static class ConverterProvidersGraph implements GraphGenerator.SemiGraph<ConverterProvider> {
        private final ConverterProvider[] myProviders;

        public ConverterProvidersGraph(ConverterProvider[] providers) {
            myProviders = providers;
        }

        @Override
        public Collection<ConverterProvider> getNodes() {
            return Arrays.asList(myProviders);
        }

        @Override
        public Iterator<ConverterProvider> getIn(ConverterProvider n) {
            List<ConverterProvider> preceding = new ArrayList<ConverterProvider>();
            for (String id : n.getPrecedingConverterIds()) {
                for (ConverterProvider provider : myProviders) {
                    if (provider.getId().equals(id)) {
                        preceding.add(provider);
                    }
                }
            }
            return preceding.iterator();
        }
    }
}