| /* |
| * Copyright 2000-2014 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.ide.fileTemplates.impl; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.fileTemplates.FileTemplate; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.ex.ProjectManagerEx; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: 3/22/11 |
| */ |
| class FTManager { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.fileTemplates.impl.FTManager"); |
| public static final String TEMPLATES_DIR = "fileTemplates"; |
| public static final String DEFAULT_TEMPLATE_EXTENSION = "ft"; |
| public static final String TEMPLATE_EXTENSION_SUFFIX = "." + DEFAULT_TEMPLATE_EXTENSION; |
| public static final String CONTENT_ENCODING = CharsetToolkit.UTF8; |
| private static final String ENCODED_NAME_EXT_DELIMITER = "\u0F0Fext\u0F0F."; |
| |
| private final String myName; |
| private final boolean myInternal; |
| private final String myTemplatesDir; |
| private final Map<String, FileTemplateBase> myTemplates = new HashMap<String, FileTemplateBase>(); |
| private volatile List<FileTemplateBase> mySortedTemplates; |
| private final List<DefaultTemplate> myDefaultTemplates = new ArrayList<DefaultTemplate>(); |
| |
| FTManager(@NotNull @NonNls String name, @NotNull @NonNls String defaultTemplatesDirName) { |
| this(name, defaultTemplatesDirName, false); |
| } |
| |
| FTManager(@NotNull @NonNls String name, @NotNull @NonNls String defaultTemplatesDirName, boolean internal) { |
| myName = name; |
| myInternal = internal; |
| myTemplatesDir = TEMPLATES_DIR + (defaultTemplatesDirName.equals(".") ? "" : File.separator + defaultTemplatesDirName); |
| } |
| |
| public String getName() { |
| return myName; |
| } |
| |
| @NotNull |
| public Collection<FileTemplateBase> getAllTemplates(boolean includeDisabled) { |
| List<FileTemplateBase> sorted = mySortedTemplates; |
| if (sorted == null) { |
| sorted = new ArrayList<FileTemplateBase>(myTemplates.values()); |
| Collections.sort(sorted, new Comparator<FileTemplateBase>() { |
| @Override |
| public int compare(FileTemplateBase t1, FileTemplateBase t2) { |
| return t1.getName().compareToIgnoreCase(t2.getName()); |
| } |
| }); |
| mySortedTemplates = sorted; |
| } |
| |
| if (includeDisabled) { |
| return Collections.unmodifiableCollection(sorted); |
| } |
| |
| final List<FileTemplateBase> list = new ArrayList<FileTemplateBase>(sorted.size()); |
| for (FileTemplateBase template : sorted) { |
| if (template instanceof BundledFileTemplate && !((BundledFileTemplate)template).isEnabled()) { |
| continue; |
| } |
| list.add(template); |
| } |
| return list; |
| } |
| |
| /** |
| * @param templateQname |
| * @return template no matter enabled or disabled it is |
| */ |
| @Nullable |
| public FileTemplateBase getTemplate(@NotNull String templateQname) { |
| return myTemplates.get(templateQname); |
| } |
| |
| /** |
| * Disabled templates are never returned |
| * @param templateName |
| * @return |
| */ |
| @Nullable |
| public FileTemplateBase findTemplateByName(@NotNull String templateName) { |
| final FileTemplateBase template = myTemplates.get(templateName); |
| if (template != null) { |
| final boolean isEnabled = !(template instanceof BundledFileTemplate) || ((BundledFileTemplate)template).isEnabled(); |
| if (isEnabled) { |
| return template; |
| } |
| } |
| // templateName must be non-qualified name, since previous lookup found nothing |
| for (FileTemplateBase t : getAllTemplates(false)) { |
| final String qName = t.getQualifiedName(); |
| if (qName.startsWith(templateName) && qName.length() > templateName.length()) { |
| String remainder = qName.substring(templateName.length()); |
| if (remainder.startsWith(ENCODED_NAME_EXT_DELIMITER) || remainder.charAt(0) == '.') { |
| return t; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @NotNull |
| public FileTemplateBase addTemplate(String name, String extension) { |
| final String qName = FileTemplateBase.getQualifiedName(name, extension); |
| FileTemplateBase template = getTemplate(qName); |
| if (template == null) { |
| template = new CustomFileTemplate(name, extension); |
| myTemplates.put(qName, template); |
| mySortedTemplates = null; |
| } |
| else { |
| if (template instanceof BundledFileTemplate && !((BundledFileTemplate)template).isEnabled()) { |
| ((BundledFileTemplate)template).setEnabled(true); |
| } |
| } |
| return template; |
| } |
| |
| public void removeTemplate(@NotNull String qName) { |
| final FileTemplateBase template = myTemplates.get(qName); |
| if (template instanceof CustomFileTemplate) { |
| myTemplates.remove(qName); |
| mySortedTemplates = null; |
| } |
| else if (template instanceof BundledFileTemplate){ |
| ((BundledFileTemplate)template).setEnabled(false); |
| } |
| } |
| |
| public void updateTemplates(@NotNull Collection<FileTemplate> newTemplates) { |
| final Set<String> toDisable = new HashSet<String>(); |
| for (DefaultTemplate template : myDefaultTemplates) { |
| toDisable.add(template.getQualifiedName()); |
| } |
| for (FileTemplate template : newTemplates) { |
| toDisable.remove(((FileTemplateBase)template).getQualifiedName()); |
| } |
| myTemplates.clear(); |
| mySortedTemplates = null; |
| for (DefaultTemplate template : myDefaultTemplates) { |
| final BundledFileTemplate bundled = createAndStoreBundledTemplate(template); |
| if (toDisable.contains(bundled.getQualifiedName())) { |
| bundled.setEnabled(false); |
| } |
| } |
| for (FileTemplate template : newTemplates) { |
| final FileTemplateBase _template = addTemplate(template.getName(), template.getExtension()); |
| _template.setText(template.getText()); |
| _template.setReformatCode(template.isReformatCode()); |
| } |
| } |
| |
| public void addDefaultTemplate(DefaultTemplate template) { |
| myDefaultTemplates.add(template); |
| createAndStoreBundledTemplate(template); |
| } |
| |
| private BundledFileTemplate createAndStoreBundledTemplate(DefaultTemplate template) { |
| final BundledFileTemplate bundled = new BundledFileTemplate(template, myInternal); |
| final String qName = bundled.getQualifiedName(); |
| final FileTemplateBase previous = myTemplates.put(qName, bundled); |
| mySortedTemplates = null; |
| |
| LOG.assertTrue(previous == null, "Duplicate bundled template " + qName + |
| " [" + template.getTemplateURL() + ", " + previous + ']'); |
| return bundled; |
| } |
| |
| public void saveTemplates() { |
| final File configRoot = getConfigRoot(true); |
| |
| final File[] files = configRoot.listFiles(); |
| |
| final Set<String> allNames = new HashSet<String>(); |
| final Map<String, File> templatesOnDisk = files != null && files.length > 0? new HashMap<String, File>() : Collections.<String, File>emptyMap(); |
| if (files != null) { |
| for (File file : files) { |
| if (!file.isDirectory()) { |
| final String name = file.getName(); |
| templatesOnDisk.put(name, file); |
| allNames.add(name); |
| } |
| } |
| } |
| |
| final Map<String, FileTemplateBase> templatesToSave = new HashMap<String, FileTemplateBase>(); |
| |
| for (FileTemplateBase template : getAllTemplates(true)) { |
| if (template instanceof BundledFileTemplate && !((BundledFileTemplate)template).isTextModified()) { |
| continue; |
| } |
| final String name = template.getQualifiedName(); |
| templatesToSave.put(name, template); |
| allNames.add(name); |
| } |
| |
| if (!allNames.isEmpty()) { |
| final String lineSeparator = CodeStyleSettingsManager.getSettings(ProjectManagerEx.getInstanceEx().getDefaultProject()).getLineSeparator(); |
| for (String name : allNames) { |
| final File customizedTemplateFile = templatesOnDisk.get(name); |
| final FileTemplateBase templateToSave = templatesToSave.get(name); |
| if (customizedTemplateFile == null) { |
| // template was not saved before |
| try { |
| saveTemplate(configRoot, templateToSave, lineSeparator); |
| } |
| catch (IOException e) { |
| LOG.error("Unable to save template " + name, e); |
| } |
| } |
| else if (templateToSave == null) { |
| // template was removed |
| FileUtil.delete(customizedTemplateFile); |
| } |
| else { |
| // both customized content on disk and corresponding template are present |
| try { |
| final String diskText = StringUtil.convertLineSeparators(FileUtil.loadFile(customizedTemplateFile, CONTENT_ENCODING)); |
| final String templateText = templateToSave.getText(); |
| if (!diskText.equals(templateText)) { |
| // save only if texts differ to avoid unnecessary file touching |
| saveTemplate(configRoot, templateToSave, lineSeparator); |
| } |
| } |
| catch (IOException e) { |
| LOG.error("Unable to save template " + name, e); |
| } |
| } |
| } |
| } |
| } |
| |
| /** Save template to file. If template is new, it is saved to specified directory. Otherwise it is saved to file from which it was read. |
| * If template was not modified, it is not saved. |
| * todo: review saving algorithm |
| */ |
| private static void saveTemplate(File parentDir, FileTemplateBase template, final String lineSeparator) throws IOException { |
| final File templateFile = new File(parentDir, encodeFileName(template.getName(), template.getExtension())); |
| |
| FileOutputStream fileOutputStream; |
| try { |
| fileOutputStream = new FileOutputStream(templateFile); |
| } |
| catch (FileNotFoundException e) { |
| // try to recover from the situation 'file exists, but is a directory' |
| FileUtil.delete(templateFile); |
| fileOutputStream = new FileOutputStream(templateFile); |
| } |
| OutputStreamWriter outputStreamWriter; |
| try{ |
| outputStreamWriter = new OutputStreamWriter(fileOutputStream, CONTENT_ENCODING); |
| } |
| catch (UnsupportedEncodingException e){ |
| Messages.showMessageDialog(IdeBundle.message("error.unable.to.save.file.template.using.encoding", template.getName(), |
| CONTENT_ENCODING), |
| CommonBundle.getErrorTitle(), Messages.getErrorIcon()); |
| outputStreamWriter = new OutputStreamWriter(fileOutputStream); |
| } |
| String content = template.getText(); |
| |
| if (!lineSeparator.equals("\n")){ |
| content = StringUtil.convertLineSeparators(content, lineSeparator); |
| } |
| |
| outputStreamWriter.write(content); |
| outputStreamWriter.close(); |
| fileOutputStream.close(); |
| } |
| |
| public File getConfigRoot(boolean create) { |
| final File templatesPath = new File(PathManager.getConfigPath(), myTemplatesDir); |
| if (create) { |
| final boolean created = templatesPath.mkdirs(); |
| if (!created && !templatesPath.exists()) { |
| LOG.info("Cannot create directory: " + templatesPath.getAbsolutePath()); |
| } |
| } |
| return templatesPath; |
| } |
| |
| @Override |
| public String toString() { |
| return myName + " file template manager"; |
| } |
| |
| public static String encodeFileName(String templateName, String extension) { |
| String nameExtDelimiter = extension.contains(".") ? ENCODED_NAME_EXT_DELIMITER : "."; |
| return templateName + nameExtDelimiter + extension; |
| } |
| |
| public static Pair<String,String> decodeFileName(String fileName) { |
| String name = fileName; |
| String ext = ""; |
| String nameExtDelimiter = fileName.contains(ENCODED_NAME_EXT_DELIMITER) ? ENCODED_NAME_EXT_DELIMITER : "."; |
| int extIndex = fileName.lastIndexOf(nameExtDelimiter); |
| if (extIndex >= 0) { |
| name = fileName.substring(0, extIndex); |
| ext = fileName.substring(extIndex + nameExtDelimiter.length()); |
| } |
| return Pair.create(name, ext); |
| } |
| |
| } |