blob: 328eda553ac10ec0e3db78528b967ee31215418a [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.platform.templates;
import com.intellij.CommonBundle;
import com.intellij.codeInspection.defaultFileTemplateUsage.FileHeaderChecker;
import com.intellij.ide.fileTemplates.FileTemplate;
import com.intellij.ide.fileTemplates.FileTemplateManager;
import com.intellij.ide.util.projectWizard.ProjectTemplateFileProcessor;
import com.intellij.ide.util.projectWizard.ProjectTemplateParameterFactory;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.StorageScheme;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectEx;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.FileIndex;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntObjectHashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Dmitry Avdeev
* Date: 10/5/12
*/
public class SaveProjectAsTemplateAction extends AnAction {
private static final Logger LOG = Logger.getInstance(SaveProjectAsTemplateAction.class);
private static final String PROJECT_TEMPLATE_XML = "project-template.xml";
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = getEventProject(e);
assert project != null;
StorageScheme scheme = ((ProjectEx)project).getStateStore().getStorageScheme();
if (scheme != StorageScheme.DIRECTORY_BASED) {
Messages.showErrorDialog(project, "Project templates do not support old .ipr (file-based) format.\n" +
"Please convert your project via File->Save as Directory-Based format.", CommonBundle.getErrorTitle());
return;
}
final VirtualFile descriptionFile = getDescriptionFile(project, LocalArchivedTemplate.DESCRIPTION_PATH);
final SaveProjectAsTemplateDialog dialog = new SaveProjectAsTemplateDialog(project, descriptionFile);
if (dialog.showAndGet()) {
final Module moduleToSave = dialog.getModuleToSave();
final File file = dialog.getTemplateFile();
final String description = dialog.getDescription();
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Saving Project as Template", true, PerformInBackgroundOption.DEAF) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
saveProject(project, file, moduleToSave, description, dialog.isReplaceParameters(), indicator);
}
@Override
public void onSuccess() {
Messages.showInfoMessage(FileUtil.getNameWithoutExtension(file) + " was successfully created.\n" +
"It's available now in Project Wizard", "Template Created");
}
@Override
public void onCancel() {
file.delete();
}
});
}
}
public static VirtualFile getDescriptionFile(Project project, String path) {
return VfsUtil.findRelativeFile(path, project.getBaseDir());
}
public static void saveProject(final Project project,
final File zipFile,
Module moduleToSave,
final String description,
boolean replaceParameters,
final ProgressIndicator indicator) {
final Map<String, String> parameters = computeParameters(project, replaceParameters);
indicator.setText("Saving project...");
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
project.save();
}
});
}
});
indicator.setText("Processing project files...");
ZipOutputStream stream = null;
try {
FileUtil.ensureExists(zipFile.getParentFile());
stream = new ZipOutputStream(new FileOutputStream(zipFile));
final VirtualFile dir = getDirectoryToSave(project, moduleToSave);
writeFile(LocalArchivedTemplate.DESCRIPTION_PATH, description, project, dir, stream, true);
if (replaceParameters) {
String text = getInputFieldsText(parameters);
writeFile(LocalArchivedTemplate.TEMPLATE_DESCRIPTOR, text, project, dir, stream, false);
}
FileIndex index = moduleToSave == null
? ProjectRootManager.getInstance(project).getFileIndex()
: ModuleRootManager.getInstance(moduleToSave).getFileIndex();
final ZipOutputStream finalStream = stream;
index.iterateContent(new ContentIterator() {
@Override
public boolean processFile(final VirtualFile virtualFile) {
if (!virtualFile.isDirectory()) {
final String fileName = virtualFile.getName();
indicator.setText2(fileName);
try {
String relativePath = VfsUtilCore.getRelativePath(virtualFile, dir, '/');
if (relativePath == null) {
throw new RuntimeException("Can't find relative path for " + virtualFile + " in " + dir);
}
final boolean system = Project.DIRECTORY_STORE_FOLDER.equals(virtualFile.getParent().getName());
if (system) {
if (!fileName.equals("description.html") &&
!fileName.equals(PROJECT_TEMPLATE_XML) &&
!fileName.equals("misc.xml") &&
!fileName.equals("modules.xml") &&
!fileName.equals("workspace.xml")) {
return true;
}
}
ZipUtil.addFileToZip(finalStream, new File(virtualFile.getPath()), dir.getName() + "/" + relativePath, null, null, new ZipUtil.FileContentProcessor() {
@Override
public InputStream getContent(final File file) throws IOException {
if (virtualFile.getFileType().isBinary() || PROJECT_TEMPLATE_XML.equals(virtualFile.getName())) return STANDARD.getContent(file);
String result = getEncodedContent(virtualFile, project, parameters);
return new ByteArrayInputStream(result.getBytes(TemplateModuleBuilder.UTF_8));
}
});
}
catch (IOException e) {
LOG.error(e);
}
}
indicator.checkCanceled();
return true;
}
});
}
catch (Exception ex) {
LOG.error(ex);
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
Messages.showErrorDialog(project, "Can't save project as template", "Internal Error");
}
});
}
finally {
StreamUtil.closeStream(stream);
}
}
private static void writeFile(String path,
final String text,
Project project, VirtualFile dir, ZipOutputStream stream, boolean overwrite) throws IOException {
final VirtualFile descriptionFile = getDescriptionFile(project, path);
if (descriptionFile == null) {
stream.putNextEntry(new ZipEntry(dir.getName() + "/" + path));
stream.write(text.getBytes());
stream.closeEntry();
}
else if (overwrite) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
try {
VfsUtil.saveText(descriptionFile, text);
}
catch (IOException e) {
LOG.error(e);
}
}
});
}
}
public static Map<String, String> computeParameters(final Project project, boolean replaceParameters) {
final Map<String, String> parameters = new HashMap<String, String>();
if (replaceParameters) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
ProjectTemplateParameterFactory[] extensions = Extensions.getExtensions(ProjectTemplateParameterFactory.EP_NAME);
for (ProjectTemplateParameterFactory extension : extensions) {
String value = extension.detectParameterValue(project);
if (value != null) {
parameters.put(value, extension.getParameterId());
}
}
}
});
}
return parameters;
}
public static String getEncodedContent(VirtualFile virtualFile,
Project project,
Map<String, String> parameters) throws IOException {
String text = VfsUtilCore.loadText(virtualFile);
final FileTemplate template = FileTemplateManager.getInstance().getDefaultTemplate(FileTemplateManager.FILE_HEADER_TEMPLATE_NAME);
final String templateText = template.getText();
final Pattern pattern = FileHeaderChecker.getTemplatePattern(template, project, new TIntObjectHashMap<String>());
String result = convertTemplates(text, pattern, templateText);
result = ProjectTemplateFileProcessor.encodeFile(result, virtualFile, project);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
result = result.replace(entry.getKey(), "${" + entry.getValue() + "}");
}
return result;
}
private static VirtualFile getDirectoryToSave(Project project, @Nullable Module module) {
if (module == null) {
return project.getBaseDir();
}
else {
VirtualFile moduleFile = module.getModuleFile();
assert moduleFile != null;
return moduleFile.getParent();
}
}
public static String convertTemplates(String input, Pattern pattern, String template) {
Matcher matcher = pattern.matcher(input);
int start = matcher.matches() ? matcher.start(1) : -1;
StringBuilder builder = new StringBuilder(input.length() + 10);
for (int i = 0; i < input.length(); i++) {
if (start == i) {
builder.append(template);
//noinspection AssignmentToForLoopParameter
i = matcher.end(1);
}
char c = input.charAt(i);
if (c == '$' || c == '#') {
builder.append('\\');
}
builder.append(c);
}
return builder.toString();
}
private static String getInputFieldsText(Map<String, String> parameters) {
Element element = new Element(RemoteTemplatesFactory.TEMPLATE);
for (Map.Entry<String, String> entry : parameters.entrySet()) {
Element field = new Element(ArchivedProjectTemplate.INPUT_FIELD);
field.setText(entry.getValue());
field.setAttribute(RemoteTemplatesFactory.INPUT_DEFAULT, entry.getKey());
element.addContent(field);
}
return JDOMUtil.writeElement(element);
}
@Override
public void update(AnActionEvent e) {
Project project = getEventProject(e);
e.getPresentation().setEnabled(project != null && !project.isDefault());
}
}