IDEA-129351 Cannot restore deleted live template
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateSettings.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.template.impl;
18
19 import com.intellij.AbstractBundle;
20 import com.intellij.codeInsight.CodeInsightBundle;
21 import com.intellij.codeInsight.template.Template;
22 import com.intellij.openapi.application.PathManager;
23 import com.intellij.openapi.application.ex.DecodeDefaultsUtil;
24 import com.intellij.openapi.components.*;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.options.BaseSchemeProcessor;
28 import com.intellij.openapi.options.SchemeProcessor;
29 import com.intellij.openapi.options.SchemesManager;
30 import com.intellij.openapi.options.SchemesManagerFactory;
31 import com.intellij.openapi.util.InvalidDataException;
32 import com.intellij.openapi.util.JDOMUtil;
33 import com.intellij.openapi.util.WriteExternalException;
34 import com.intellij.util.containers.MultiMap;
35 import org.jdom.Document;
36 import org.jdom.Element;
37 import org.jdom.JDOMException;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.io.File;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.util.*;
46
47
48 @State(
49   name="TemplateSettings",
50   storages= {
51     @Storage(
52       file = StoragePathMacros.APP_CONFIG + "/other.xml"
53     )}
54 )
55 public class TemplateSettings implements PersistentStateComponent<Element>, ExportableComponent {
56
57   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateSettings");
58
59   @NonNls public static final String USER_GROUP_NAME = "user";
60   @NonNls private static final String TEMPLATE_SET = "templateSet";
61   @NonNls private static final String GROUP = "group";
62   @NonNls private static final String TEMPLATE = "template";
63
64   @NonNls private static final String DELETED_TEMPLATES = "deleted_templates";
65   private final List<TemplateKey> myDeletedTemplates = new ArrayList<TemplateKey>();
66
67   public static final char SPACE_CHAR = ' ';
68   public static final char TAB_CHAR = '\t';
69   public static final char ENTER_CHAR = '\n';
70   public static final char DEFAULT_CHAR = 'D';
71
72   @NonNls private static final String SPACE = "SPACE";
73   @NonNls private static final String TAB = "TAB";
74   @NonNls private static final String ENTER = "ENTER";
75
76   @NonNls private static final String NAME = "name";
77   @NonNls private static final String VALUE = "value";
78   @NonNls private static final String DESCRIPTION = "description";
79   @NonNls private static final String SHORTCUT = "shortcut";
80
81   @NonNls private static final String VARIABLE = "variable";
82   @NonNls private static final String EXPRESSION = "expression";
83   @NonNls private static final String DEFAULT_VALUE = "defaultValue";
84   @NonNls private static final String ALWAYS_STOP_AT = "alwaysStopAt";
85
86   @NonNls private static final String CONTEXT = "context";
87   @NonNls private static final String TO_REFORMAT = "toReformat";
88   @NonNls private static final String TO_SHORTEN_FQ_NAMES = "toShortenFQNames";
89   @NonNls private static final String USE_STATIC_IMPORT = "useStaticImport";
90
91   @NonNls private static final String DEFAULT_SHORTCUT = "defaultShortcut";
92   @NonNls private static final String DEACTIVATED = "deactivated";
93
94   @NonNls private static final String RESOURCE_BUNDLE = "resource-bundle";
95   @NonNls private static final String KEY = "key";
96   @NonNls private static final String ID = "id";
97
98   @NonNls private static final String TEMPLATES_CONFIG_FOLDER = "templates";
99
100   private final MultiMap<String,TemplateImpl> myTemplates = MultiMap.createLinked();
101     
102   private final Map<String,Template> myTemplatesById = new LinkedHashMap<String,Template>();
103   private final Map<TemplateKey,TemplateImpl> myDefaultTemplates = new LinkedHashMap<TemplateKey, TemplateImpl>();
104
105   private int myMaxKeyLength = 0;
106   private char myDefaultShortcutChar = TAB_CHAR;
107   private final SchemesManager<TemplateGroup, TemplateGroup> mySchemesManager;
108   private static final String FILE_SPEC = StoragePathMacros.ROOT_CONFIG + "/templates";
109
110   public static class TemplateKey {
111     private String groupName;
112     private String key;
113
114     @SuppressWarnings("UnusedDeclaration")
115     public TemplateKey() {}
116
117     private TemplateKey(String groupName, String key) {
118       this.groupName = groupName;
119       this.key = key;
120     }
121
122     public static TemplateKey keyOf(TemplateImpl template) {
123       return new TemplateKey(template.getGroupName(), template.getKey());
124     }
125
126     public boolean equals(Object o) {
127       if (this == o) return true;
128       if (o == null || getClass() != o.getClass()) return false;
129
130       TemplateKey that = (TemplateKey)o;
131
132       if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false;
133       if (key != null ? !key.equals(that.key) : that.key != null) return false;
134
135       return true;
136     }
137
138     public int hashCode() {
139       int result = groupName != null ? groupName.hashCode() : 0;
140       result = 31 * result + (key != null ? key.hashCode() : 0);
141       return result;
142     }
143
144     public String getGroupName() {
145       return groupName;
146     }
147
148     @SuppressWarnings("UnusedDeclaration")
149     public void setGroupName(String groupName) {
150       this.groupName = groupName;
151     }
152
153     public String getKey() {
154       return key;
155     }
156
157     public void setKey(String key) {
158       this.key = key;
159     }
160
161     @Override
162     public String toString() {
163       return getKey()+"@" + getGroupName();
164     }
165   }
166
167   private TemplateKey myLastSelectedTemplate;
168
169   public TemplateSettings(SchemesManagerFactory schemesManagerFactory) {
170
171     SchemeProcessor<TemplateGroup> processor = new BaseSchemeProcessor<TemplateGroup>() {
172       @Override
173       @Nullable
174       public TemplateGroup readScheme(@NotNull final Document schemeContent)
175         throws InvalidDataException, IOException, JDOMException {
176         return readTemplateFile(schemeContent, schemeContent.getRootElement().getAttributeValue("group"), false, false,
177                                 getClass().getClassLoader());
178       }
179
180
181       @Override
182       public boolean shouldBeSaved(@NotNull final TemplateGroup template) {
183         for (TemplateImpl t : template.getElements()) {
184           if (differsFromDefault(t)) {
185             return true;
186           }
187         }
188         return false;
189       }
190
191       @Override
192       public Element writeScheme(@NotNull final TemplateGroup template) throws WriteExternalException {
193         Element templateSetElement = new Element(TEMPLATE_SET);
194         templateSetElement.setAttribute(GROUP, template.getName());
195
196         for (TemplateImpl t : template.getElements()) {
197           if (differsFromDefault(t)) {
198             saveTemplate(t, templateSetElement);
199           }
200         }
201
202         return templateSetElement;
203       }
204
205       @Override
206       public void initScheme(@NotNull final TemplateGroup scheme) {
207         Collection<TemplateImpl> templates = scheme.getElements();
208
209         for (TemplateImpl template : templates) {
210           addTemplateImpl(template);
211         }
212       }
213
214       @Override
215       public void onSchemeAdded(@NotNull final TemplateGroup scheme) {
216         for (TemplateImpl template : scheme.getElements()) {
217           addTemplateImpl(template);
218         }
219       }
220
221       @Override
222       public void onSchemeDeleted(@NotNull final TemplateGroup scheme) {
223         for (TemplateImpl template : scheme.getElements()) {
224           removeTemplate(template);
225         }
226       }
227     };
228
229     mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, processor, RoamingType.PER_USER);
230
231     loadTemplates();
232   }
233
234   private boolean differsFromDefault(TemplateImpl t) {
235     TemplateImpl def = getDefaultTemplate(t);
236     if (def == null) return true;
237     return !t.equals(def) || !t.contextsEqual(def);
238   }
239
240   @Nullable
241   public TemplateImpl getDefaultTemplate(TemplateImpl t) {
242     return myDefaultTemplates.get(TemplateKey.keyOf(t));
243   }
244
245   @Override
246   @NotNull
247   public File[] getExportFiles() {
248     File exportableSettingsFile =
249       new File(PathManager.getOptionsPath() + File.separator + ExportableTemplateSettings.EXPORTABLE_SETTINGS_FILE);
250     return new File[]{getTemplateDirectory(true), exportableSettingsFile };
251   }
252
253   @Override
254   @NotNull
255   public String getPresentableName() {
256     return CodeInsightBundle.message("templates.export.display.name");
257   }
258
259   public static TemplateSettings getInstance() {
260     return ServiceManager.getService(TemplateSettings.class);
261   }
262
263   @Override
264   public void loadState(Element parentNode) {
265     Element element = parentNode.getChild(DEFAULT_SHORTCUT);
266     if (element != null) {
267       String shortcut = element.getAttributeValue(SHORTCUT);
268       if (TAB.equals(shortcut)) {
269         myDefaultShortcutChar = TAB_CHAR;
270       } else if (ENTER.equals(shortcut)) {
271         myDefaultShortcutChar = ENTER_CHAR;
272       } else {
273         myDefaultShortcutChar = SPACE_CHAR;
274       }
275     }
276
277     ExportableTemplateSettings exportableSettings = ServiceManager.getService(ExportableTemplateSettings.class);
278     assert exportableSettings != null : "Can't find required ExportableTemplateSettings service.";
279     exportableSettings.setParentSettings(this);
280     if (exportableSettings.isLoaded()) {
281       myDeletedTemplates.addAll(exportableSettings.getDeletedKeys());
282     }
283     else {
284       Element deleted = parentNode.getChild(DELETED_TEMPLATES);
285       if (deleted != null) {
286         List children = deleted.getChildren();
287         for (final Object aChildren : children) {
288           Element child = (Element)aChildren;
289           myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(GROUP), child.getAttributeValue(NAME)));
290         }
291       }
292     }
293
294     for (TemplateKey templateKey : myDeletedTemplates) {
295       if (templateKey.groupName == null) {
296         final Collection<TemplateImpl> templates = new ArrayList<TemplateImpl>(myTemplates.get(templateKey.key));
297         for (TemplateImpl template : templates) {
298           removeTemplate(template);
299         }
300       }
301       else {
302         final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
303         if (toDelete != null) {
304           removeTemplate(toDelete);
305         }
306       }
307     }
308
309     //TODO lesya reload schemes
310   }
311
312   @Override
313   public Element getState()  {
314     Element parentNode = new Element("TemplateSettings");
315     Element element = new Element(DEFAULT_SHORTCUT);
316     if (myDefaultShortcutChar == TAB_CHAR) {
317       element.setAttribute(SHORTCUT, TAB);
318     } else if (myDefaultShortcutChar == ENTER_CHAR) {
319       element.setAttribute(SHORTCUT, ENTER);
320     } else {
321       element.setAttribute(SHORTCUT, SPACE);
322     }
323     parentNode.addContent(element);
324
325     return parentNode;
326   }
327
328   @Nullable
329   public String getLastSelectedTemplateKey() {
330     return myLastSelectedTemplate != null ? myLastSelectedTemplate.key : null;
331   }
332
333   @Nullable
334   public String getLastSelectedTemplateGroup() {
335     return myLastSelectedTemplate != null ? myLastSelectedTemplate.groupName : null;
336   }
337
338   public void setLastSelectedTemplate(@Nullable String group, @Nullable String key) {
339     myLastSelectedTemplate = group == null ? null : new TemplateKey(group, key);
340   }
341
342   public Collection<? extends TemplateImpl> getTemplatesAsList() {
343     return myTemplates.values();
344   }
345
346   public TemplateImpl[] getTemplates() {
347     final Collection<? extends TemplateImpl> all = myTemplates.values();
348     return all.toArray(new TemplateImpl[all.size()]);
349   }
350
351   public char getDefaultShortcutChar() {
352     return myDefaultShortcutChar;
353   }
354
355   public void setDefaultShortcutChar(char defaultShortcutChar) {
356     myDefaultShortcutChar = defaultShortcutChar;
357   }
358
359   public Collection<TemplateImpl> getTemplates(@NonNls String key) {
360     return myTemplates.get(key);
361   }
362
363   @Nullable
364   public TemplateImpl getTemplate(@NonNls String key, String group) {
365     final Collection<TemplateImpl> templates = myTemplates.get(key);
366     for (TemplateImpl template : templates) {
367       if (template.getGroupName().equals(group)) {
368         return template;
369       }
370     }
371     return null;
372   }
373
374   public Template getTemplateById(@NonNls String id) {
375     return myTemplatesById.get(id);
376   }
377
378   public int getMaxKeyLength() {
379     return myMaxKeyLength;
380   }
381
382   public void addTemplate(Template template) {
383     clearPreviouslyRegistered(template);
384     addTemplateImpl(template);
385
386     TemplateImpl templateImpl = (TemplateImpl)template;
387     String groupName = templateImpl.getGroupName();
388     TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
389     if (group == null) {
390       group = new TemplateGroup(groupName);
391       mySchemesManager.addNewScheme(group, true);
392     }
393     group.addElement(templateImpl);
394   }
395
396   private void clearPreviouslyRegistered(final Template template) {
397     TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
398     if (existing != null) {
399       LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
400       TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName());
401       if (group != null) {
402         group.removeElement(existing);
403         if (group.isEmpty()) {
404           mySchemesManager.removeScheme(group);
405         }
406       }
407       myTemplates.remove(template.getKey(), existing);
408     }
409   }
410
411   private void addTemplateImpl(Template template) {
412     final TemplateImpl templateImpl = (TemplateImpl)template;
413     if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
414       myTemplates.putValue(template.getKey(), templateImpl);
415     }
416
417     myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
418     myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template));
419
420   }
421
422   private void addTemplateById(Template template) {
423     if (!myTemplatesById.containsKey(template.getId())) {
424       final String id = template.getId();
425       if (id != null) {
426         myTemplatesById.put(id, template);
427       }
428     }
429   }
430
431   public void removeTemplate(Template template) {
432     myTemplates.remove(template.getKey(), (TemplateImpl)template);
433
434     TemplateImpl templateImpl = (TemplateImpl)template;
435     String groupName = templateImpl.getGroupName();
436     TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
437
438     if (group != null) {
439       group.removeElement((TemplateImpl)template);
440       if (group.isEmpty()) {
441         mySchemesManager.removeScheme(group);
442       }
443
444     }
445   }
446
447   private TemplateImpl addTemplate(String key, String string, String group, String description, String shortcut, boolean isDefault,
448                                    final String id) {
449     TemplateImpl template = new TemplateImpl(key, string, group);
450     template.setId(id);
451     template.setDescription(description);
452     if (TAB.equals(shortcut)) {
453       template.setShortcutChar(TAB_CHAR);
454     } else if (ENTER.equals(shortcut)) {
455       template.setShortcutChar(ENTER_CHAR);
456     } else if (SPACE.equals(shortcut)) {
457       template.setShortcutChar(SPACE_CHAR);
458     } else {
459       template.setShortcutChar(DEFAULT_CHAR);
460     }
461     if (isDefault) {
462       myDefaultTemplates.put(TemplateKey.keyOf(template), template);
463     }
464     return template;
465   }
466
467   @Nullable
468   private static File getTemplateDirectory(boolean toCreate) {
469     String directoryPath = PathManager.getConfigPath() + File.separator + TEMPLATES_CONFIG_FOLDER;
470     File directory = new File(directoryPath);
471     if (!directory.exists()) {
472       if (!toCreate) {
473         return null;
474       }
475       if (!directory.mkdir()) {
476         if (LOG.isDebugEnabled()) {
477           LOG.debug("cannot create directory: " + directory.getAbsolutePath());
478         }
479         return null;
480       }
481     }
482     return directory;
483   }
484
485   private void loadTemplates() {
486     Collection<TemplateGroup> loaded = mySchemesManager.loadSchemes();
487     for (TemplateGroup group : loaded) {
488       Collection<TemplateImpl> templates = group.getElements();
489
490       for (TemplateImpl template : templates) {
491         addTemplateImpl(template);
492       }
493     }
494
495     loadDefaultLiveTemplates();
496   }
497
498   private void loadDefaultLiveTemplates() {
499     try {
500       for(DefaultLiveTemplatesProvider provider: Extensions.getExtensions(DefaultLiveTemplatesProvider.EP_NAME)) {
501         for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
502           readDefTemplate(provider, defTemplate, true);
503         }
504         try {
505           String[] hidden = provider.getHiddenLiveTemplateFiles();
506           if (hidden != null) {
507             for (String s : hidden) {
508               readDefTemplate(provider, s, false);
509             }
510           }
511         }
512         catch (AbstractMethodError ignore) {
513         }
514       }
515     } catch (Exception e) {
516       LOG.error(e);
517     }
518   }
519
520   private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate)
521     throws JDOMException, InvalidDataException, IOException {
522     String templateName = getDefaultTemplateName(defTemplate);
523     InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
524     if (inputStream != null) {
525       TemplateGroup group =
526         readTemplateFile(JDOMUtil.loadDocument(inputStream), templateName, true, registerTemplate, provider.getClass().getClassLoader());
527       if (group != null && group.getReplace() != null) {
528         Collection<TemplateImpl> templates = myTemplates.get(group.getReplace());
529         for (TemplateImpl template : templates) {
530           removeTemplate(template);
531         }
532       }
533     }
534   }
535
536   private static String getDefaultTemplateName(String defTemplate) {
537     return defTemplate.substring(defTemplate.lastIndexOf("/") + 1);
538   }
539
540   @Nullable
541   private TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, ClassLoader classLoader) throws InvalidDataException {
542     if (document == null) {
543       throw new InvalidDataException();
544     }
545     Element root = document.getRootElement();
546     if (root == null || !TEMPLATE_SET.equals(root.getName())) {
547       throw new InvalidDataException();
548     }
549
550     String groupName = root.getAttributeValue(GROUP);
551     if (groupName == null || groupName.isEmpty()) groupName = defGroupName;
552
553     TemplateGroup result = new TemplateGroup(groupName, root.getAttributeValue("REPLACE"));
554
555     Map<String, TemplateImpl> created = new LinkedHashMap<String,  TemplateImpl>();
556
557     for (final Object o1 : root.getChildren(TEMPLATE)) {
558       Element element = (Element)o1;
559
560       TemplateImpl template = readTemplateFromElement(isDefault, groupName, element, classLoader);
561       TemplateImpl existing = getTemplate(template.getKey(), template.getGroupName());
562       boolean defaultTemplateModified = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) ||
563                                                       myTemplatesById.containsKey(template.getId()) ||
564                                                       existing != null);
565
566       if(!defaultTemplateModified) {
567         created.put(template.getKey(), template);
568       }
569       if (isDefault && existing != null) {
570         existing.getTemplateContext().setDefaultContext(template.getTemplateContext());
571       }
572     }
573
574     if (registerTemplate) {
575       TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
576       if (existingScheme != null) {
577         result = existingScheme;
578       }
579     }
580
581     for (TemplateImpl template : created.values()) {
582       if (registerTemplate) {
583         clearPreviouslyRegistered(template);
584         addTemplateImpl(template);
585       }
586       addTemplateById(template);
587
588       result.addElement(template);
589     }
590
591     if (registerTemplate) {
592       TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
593       if (existingScheme == null && !result.isEmpty()) {
594         mySchemesManager.addNewScheme(result, false);
595       }
596     }
597
598     return result.isEmpty() ? null : result;
599
600   }
601
602   private TemplateImpl readTemplateFromElement(final boolean isDefault,
603                                                final String groupName,
604                                                final Element element,
605                                                ClassLoader classLoader) throws InvalidDataException {
606     String name = element.getAttributeValue(NAME);
607     String value = element.getAttributeValue(VALUE);
608     String description;
609     String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
610     String key = element.getAttributeValue(KEY);
611     String id = element.getAttributeValue(ID);
612     if (resourceBundle != null && key != null) {
613       if (classLoader == null) {
614         classLoader = getClass().getClassLoader();
615       }
616       ResourceBundle bundle = AbstractBundle.getResourceBundle(resourceBundle, classLoader);
617       description = bundle.getString(key);
618     }
619     else {
620       description = element.getAttributeValue(DESCRIPTION);
621     }
622     String shortcut = element.getAttributeValue(SHORTCUT);
623     TemplateImpl template = addTemplate(name, value, groupName, description, shortcut, isDefault, id);
624
625     template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
626     template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
627     template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
628
629     String useStaticImport = element.getAttributeValue(USE_STATIC_IMPORT);
630     if (useStaticImport != null) {
631       template.setValue(TemplateImpl.Property.USE_STATIC_IMPORT_IF_POSSIBLE, Boolean.parseBoolean(useStaticImport));
632     }
633
634     for (final Object o : element.getChildren(VARIABLE)) {
635       Element e = (Element)o;
636       String variableName = e.getAttributeValue(NAME);
637       String expression = e.getAttributeValue(EXPRESSION);
638       String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
639       boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
640       template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
641     }
642
643     Element context = element.getChild(CONTEXT);
644     if (context != null) {
645       template.getTemplateContext().readTemplateContext(context);
646     }
647
648     return template;
649   }
650
651   private void saveTemplate(TemplateImpl template, Element templateSetElement) {
652     Element element = new Element(TEMPLATE);
653     final String id = template.getId();
654     if (id != null) {
655       element.setAttribute(ID, id);
656     }
657     element.setAttribute(NAME, template.getKey());
658     element.setAttribute(VALUE, template.getString());
659     if (template.getShortcutChar() == TAB_CHAR) {
660       element.setAttribute(SHORTCUT, TAB);
661     } else if (template.getShortcutChar() == ENTER_CHAR) {
662       element.setAttribute(SHORTCUT, ENTER);
663     } else if (template.getShortcutChar() == SPACE_CHAR) {
664       element.setAttribute(SHORTCUT, SPACE);
665     }
666     if (template.getDescription() != null) {
667       element.setAttribute(DESCRIPTION, template.getDescription());
668     }
669     element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
670     element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
671     if (template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)
672         != Template.getDefaultValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE))
673     {
674       element.setAttribute(USE_STATIC_IMPORT, Boolean.toString(template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)));
675     }
676     if (template.isDeactivated()) {
677       element.setAttribute(DEACTIVATED, Boolean.toString(true));
678     }
679
680     for (int i = 0; i < template.getVariableCount(); i++) {
681       Element variableElement = new Element(VARIABLE);
682       variableElement.setAttribute(NAME, template.getVariableNameAt(i));
683       variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
684       variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
685       variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
686       element.addContent(variableElement);
687     }
688
689     try {
690       Element contextElement = new Element(CONTEXT);
691       TemplateImpl def = getDefaultTemplate(template);
692       template.getTemplateContext().writeTemplateContext(contextElement, def == null ? null : def.getTemplateContext());
693       element.addContent(contextElement);
694     } catch (WriteExternalException ignore) {
695     }
696     templateSetElement.addContent(element);
697   }
698
699   public void setTemplates(List<TemplateGroup> newGroups) {
700     myTemplates.clear();
701     myDeletedTemplates.clear();
702     for (TemplateImpl template : myDefaultTemplates.values()) {
703       myDeletedTemplates.add(TemplateKey.keyOf(template));
704     }
705     mySchemesManager.clearAllSchemes();
706     myMaxKeyLength = 0;
707     for (TemplateGroup group : newGroups) {
708       if (!group.isEmpty()) {
709         mySchemesManager.addNewScheme(group, true);
710         for (TemplateImpl template : group.getElements()) {
711           clearPreviouslyRegistered(template);
712           addTemplateImpl(template);
713         }
714       }
715     }
716   }
717
718   public SchemesManager<TemplateGroup,TemplateGroup> getSchemesManager() {
719     return mySchemesManager;
720   }
721
722   public List<TemplateGroup> getTemplateGroups() {
723     return mySchemesManager.getAllSchemes();
724   }
725
726   public List<TemplateImpl> collectMatchingCandidates(String key, @Nullable Character shortcutChar, boolean hasArgument) {
727     final Collection<TemplateImpl> templates = getTemplates(key);
728     List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
729     for (TemplateImpl template : templates) {
730       if (template.isDeactivated()) {
731         continue;
732       }
733       if (shortcutChar != null && getShortcutChar(template) != shortcutChar) {
734         continue;
735       }
736       if (hasArgument && !template.hasArgument()) {
737         continue;
738       }
739       candidates.add(template);
740     }
741     return candidates;
742   }
743
744   public char getShortcutChar(TemplateImpl template) {
745     char c = template.getShortcutChar();
746     if (c == DEFAULT_CHAR) {
747       return getDefaultShortcutChar();
748     }
749     else {
750       return c;
751     }
752   }
753
754   public List<TemplateKey> getDeletedTemplates() {
755     return myDeletedTemplates;
756   }
757
758   public void reset() {
759     myDeletedTemplates.clear();
760     loadDefaultLiveTemplates();
761   }
762 }