| /* |
| * 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()); |
| } |
| } |