blob: 99db0269d09146a970cd6c0f6a53d3a5c1410209 [file] [log] [blame]
/*
* Copyright 2000-2013 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) {
try {
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);
}
catch (CannotConvertException e) {
LOG.info("Cannot check whether conversion of project files is needed or not, conversion won't be performed", e);
}
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();
}
}
}