IDEADEV-41040
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateSettings.java
blobc8ecbbfc62eff34c18309b772a9ba54929b09276
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.template.impl;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.template.Template;
21 import com.intellij.openapi.application.PathManager;
22 import com.intellij.openapi.application.ex.DecodeDefaultsUtil;
23 import com.intellij.openapi.components.*;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.extensions.Extensions;
26 import com.intellij.openapi.options.Scheme;
27 import com.intellij.openapi.options.SchemeProcessor;
28 import com.intellij.openapi.options.SchemesManager;
29 import com.intellij.openapi.options.SchemesManagerFactory;
30 import com.intellij.openapi.util.InvalidDataException;
31 import com.intellij.openapi.util.JDOMUtil;
32 import com.intellij.openapi.util.WriteExternalException;
33 import com.intellij.util.containers.MultiMap;
34 import org.jdom.Document;
35 import org.jdom.Element;
36 import org.jdom.JDOMException;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.util.*;
47 @State(
48 name="TemplateSettings",
49 storages= {
50 @Storage(
51 id="other",
52 file = "$APP_CONFIG$/other.xml"
55 public class TemplateSettings implements PersistentStateComponent<Element>, ExportableComponent {
57 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateSettings");
59 public @NonNls static final String USER_GROUP_NAME = "user";
60 private @NonNls static final String TEMPLATE_SET = "templateSet";
61 private @NonNls static final String GROUP = "group";
62 private @NonNls static final String TEMPLATE = "template";
64 private @NonNls static final String DELETED_TEMPLATES = "deleted_templates";
65 private final List<TemplateKey> myDeletedTemplates = new ArrayList<TemplateKey>();
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';
72 private static final @NonNls String SPACE = "SPACE";
73 private static final @NonNls String TAB = "TAB";
74 private static final @NonNls String ENTER = "ENTER";
76 private static final @NonNls String NAME = "name";
77 private static final @NonNls String VALUE = "value";
78 private static final @NonNls String DESCRIPTION = "description";
79 private static final @NonNls String SHORTCUT = "shortcut";
81 private static final @NonNls String VARIABLE = "variable";
82 private static final @NonNls String EXPRESSION = "expression";
83 private static final @NonNls String DEFAULT_VALUE = "defaultValue";
84 private static final @NonNls String ALWAYS_STOP_AT = "alwaysStopAt";
86 private static final @NonNls String CONTEXT = "context";
87 private static final @NonNls String TO_REFORMAT = "toReformat";
88 private static final @NonNls String TO_SHORTEN_FQ_NAMES = "toShortenFQNames";
90 private static final @NonNls String DEFAULT_SHORTCUT = "defaultShortcut";
91 private static final @NonNls String DEACTIVATED = "deactivated";
93 @NonNls private static final String RESOURCE_BUNDLE = "resource-bundle";
94 @NonNls private static final String KEY = "key";
95 @NonNls private static final String ID = "id";
97 private static final @NonNls String TEMPLATES_CONFIG_FOLDER = "templates";
99 private final List<TemplateImpl> myAllTemplates = new ArrayList<TemplateImpl>();
100 private final MultiMap<String,TemplateImpl> myTemplates = new MultiMap<String,TemplateImpl>();
101 private final Map<String,Template> myTemplatesById = new LinkedHashMap<String,Template>();
102 private final Map<String,TemplateImpl> myDefaultTemplates = new LinkedHashMap<String, TemplateImpl>();
104 private int myMaxKeyLength = 0;
105 private char myDefaultShortcutChar = TAB_CHAR;
106 private String myLastSelectedTemplateKey;
107 @NonNls
108 public static final String XML_EXTENSION = ".xml";
109 private final SchemesManager<TemplateGroup, TemplateGroup> mySchemesManager;
110 private final SchemeProcessor<TemplateGroup> myProcessor;
111 private static final String FILE_SPEC = "$ROOT_CONFIG$/templates";
113 private static class TemplateKey {
114 final String groupName;
115 final String key;
117 private TemplateKey(String groupName, String key) {
118 this.groupName = groupName;
119 this.key = key;
122 public static TemplateKey keyOf(TemplateImpl template) {
123 return new TemplateKey(template.getGroupName(), template.getKey());
127 public TemplateSettings(SchemesManagerFactory schemesManagerFactory) {
130 myProcessor = new SchemeProcessor<TemplateGroup>() {
131 public TemplateGroup readScheme(final Document schemeContent)
132 throws InvalidDataException, IOException, JDOMException {
133 return readTemplateFile(schemeContent, schemeContent.getRootElement().getAttributeValue("group"), false, false);
137 public boolean shouldBeSaved(final TemplateGroup template) {
138 for (TemplateImpl t : template.getElements()) {
139 if (differsFromDefault(t)) {
140 return true;
143 return false;
146 public Document writeScheme(final TemplateGroup template) throws WriteExternalException {
147 Element templateSetElement = new Element(TEMPLATE_SET);
148 templateSetElement.setAttribute(GROUP, template.getName());
150 for (TemplateImpl t : template.getElements()) {
151 if (differsFromDefault(t)) {
152 saveTemplate(t, templateSetElement);
156 return new Document(templateSetElement);
159 public void initScheme(final TemplateGroup scheme) {
160 Collection<TemplateImpl> templates = scheme.getElements();
162 for (TemplateImpl template : templates) {
163 addTemplateImpl(template);
167 public void onSchemeAdded(final TemplateGroup scheme) {
168 for (TemplateImpl template : scheme.getElements()) {
169 addTemplateImpl(template);
173 public void onSchemeDeleted(final TemplateGroup scheme) {
174 for (TemplateImpl template : scheme.getElements()) {
175 removeTemplate(template);
179 public void onCurrentSchemeChanged(final Scheme newCurrentScheme) {
184 mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, myProcessor, RoamingType.PER_USER);
186 loadTemplates();
189 private boolean differsFromDefault(TemplateImpl t) {
190 TemplateImpl def = myDefaultTemplates.get(t.getKey());
191 if (def == null) return true;
192 return !t.equals(def) || !t.contextsEqual(def);
195 @NotNull
196 public File[] getExportFiles() {
197 return new File[]{getTemplateDirectory(true),PathManager.getDefaultOptionsFile()};
200 @NotNull
201 public String getPresentableName() {
202 return CodeInsightBundle.message("templates.export.display.name");
205 public static TemplateSettings getInstance() {
206 return ServiceManager.getService(TemplateSettings.class);
209 public void loadState(Element parentNode) {
210 Element element = parentNode.getChild(DEFAULT_SHORTCUT);
211 if (element != null) {
212 String shortcut = element.getAttributeValue(SHORTCUT);
213 if (TAB.equals(shortcut)) {
214 myDefaultShortcutChar = TAB_CHAR;
215 } else if (ENTER.equals(shortcut)) {
216 myDefaultShortcutChar = ENTER_CHAR;
217 } else {
218 myDefaultShortcutChar = SPACE_CHAR;
222 Element deleted = parentNode.getChild(DELETED_TEMPLATES);
223 if (deleted != null) {
224 List children = deleted.getChildren();
225 for (final Object aChildren : children) {
226 Element child = (Element)aChildren;
227 myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(NAME), child.getAttributeValue(GROUP)));
231 for (TemplateKey templateKey : myDeletedTemplates) {
232 if (templateKey.groupName == null) {
233 final Collection<TemplateImpl> templates = myTemplates.get(templateKey.key);
234 for (TemplateImpl template : templates) {
235 removeTemplate(template);
238 else {
239 final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
240 if (toDelete != null) {
241 removeTemplate(toDelete);
246 //TODO lesya reload schemes
249 public Element getState() {
250 Element parentNode = new Element("TemplateSettings");
251 Element element = new Element(DEFAULT_SHORTCUT);
252 if (myDefaultShortcutChar == TAB_CHAR) {
253 element.setAttribute(SHORTCUT, TAB);
254 } else if (myDefaultShortcutChar == ENTER_CHAR) {
255 element.setAttribute(SHORTCUT, ENTER);
256 } else {
257 element.setAttribute(SHORTCUT, SPACE);
259 parentNode.addContent(element);
261 if (myDeletedTemplates.size() > 0) {
262 Element deleted = new Element(DELETED_TEMPLATES);
263 for (final TemplateKey deletedTemplate : myDeletedTemplates) {
264 if (deletedTemplate.key != null) {
265 Element template = new Element(TEMPLATE);
266 template.setAttribute(NAME, deletedTemplate.key);
267 if (deletedTemplate.groupName != null) {
268 template.setAttribute(GROUP, deletedTemplate.groupName);
270 deleted.addContent(template);
273 parentNode.addContent(deleted);
275 return parentNode;
278 public String getLastSelectedTemplateKey() {
279 return myLastSelectedTemplateKey;
282 public void setLastSelectedTemplateKey(String key) {
283 myLastSelectedTemplateKey = key;
286 public TemplateImpl[] getTemplates() {
287 return myAllTemplates.toArray(new TemplateImpl[myAllTemplates.size()]);
290 public char getDefaultShortcutChar() {
291 return myDefaultShortcutChar;
294 public void setDefaultShortcutChar(char defaultShortcutChar) {
295 myDefaultShortcutChar = defaultShortcutChar;
298 public Collection<TemplateImpl> getTemplates(@NonNls String key) {
299 return myTemplates.get(key);
302 public TemplateImpl getTemplate(@NonNls String key, String group) {
303 final Collection<TemplateImpl> templates = myTemplates.get(key);
304 for (TemplateImpl template : templates) {
305 if (template.getGroupName().equals(group)) {
306 return template;
309 return null;
312 public Template getTemplateById(@NonNls String id) {
313 return myTemplatesById.get(id);
316 public int getMaxKeyLength() {
317 return myMaxKeyLength;
320 public void addTemplate(Template template) {
321 clearPreviouslyRegistered(template);
322 addTemplateImpl(template);
324 TemplateImpl templateImpl = (TemplateImpl)template;
325 String groupName = templateImpl.getGroupName();
326 TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
327 if (group == null) {
328 group = new TemplateGroup(groupName);
329 mySchemesManager.addNewScheme(group, true);
331 group.addElement(templateImpl);
334 private void clearPreviouslyRegistered(final Template template) {
335 TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
336 if (existing != null) {
337 LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
338 TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName());
339 if (group != null) {
340 group.removeElement(existing);
341 if (group.isEmpty()) {
342 mySchemesManager.removeScheme(group);
345 myTemplates.removeValue(template.getKey(), existing);
349 private void addTemplateImpl(Template template) {
350 final TemplateImpl templateImpl = (TemplateImpl)template;
351 if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
352 myTemplates.putValue(template.getKey(), templateImpl);
353 myAllTemplates.add(templateImpl);
356 myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
357 myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template));
361 private void addTemplateById(Template template) {
362 if (!myTemplatesById.containsKey(template.getId())) {
363 final String id = template.getId();
364 if (id != null) {
365 myTemplatesById.put(id, template);
370 public void removeTemplate(Template template) {
371 myTemplates.removeValue(template.getKey(), (TemplateImpl )template);
373 TemplateImpl templateImpl = (TemplateImpl)template;
374 myAllTemplates.remove(templateImpl);
375 String groupName = templateImpl.getGroupName();
376 TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
378 if (group != null) {
379 group.removeElement((TemplateImpl)template);
380 if (group.isEmpty()) {
381 mySchemesManager.removeScheme(group);
387 private TemplateImpl addTemplate(String key, String string, String group, String description, String shortcut, boolean isDefault,
388 final String id) {
389 TemplateImpl template = new TemplateImpl(key, string, group);
390 template.setId(id);
391 template.setDescription(description);
392 if (TAB.equals(shortcut)) {
393 template.setShortcutChar(TAB_CHAR);
394 } else if (ENTER.equals(shortcut)) {
395 template.setShortcutChar(ENTER_CHAR);
396 } else if (SPACE.equals(shortcut)) {
397 template.setShortcutChar(SPACE_CHAR);
398 } else {
399 template.setShortcutChar(DEFAULT_CHAR);
401 if (isDefault) {
402 myDefaultTemplates.put(key, template);
404 return template;
407 @Nullable
408 private static File getTemplateDirectory(boolean toCreate) {
409 String directoryPath = PathManager.getConfigPath() + File.separator + TEMPLATES_CONFIG_FOLDER;
410 File directory = new File(directoryPath);
411 if (!directory.exists()) {
412 if (!toCreate) {
413 return null;
415 if (!directory.mkdir()) {
416 if (LOG.isDebugEnabled()) {
417 LOG.debug("cannot create directory: " + directory.getAbsolutePath());
419 return null;
422 return directory;
425 private void loadTemplates() {
427 Collection<TemplateGroup> loaded = mySchemesManager.loadSchemes();
428 for (TemplateGroup group : loaded) {
429 Collection<TemplateImpl> templates = group.getElements();
431 for (TemplateImpl template : templates) {
432 addTemplateImpl(template);
438 try {
439 for(DefaultLiveTemplatesProvider provider: Extensions.getExtensions(DefaultLiveTemplatesProvider.EP_NAME)) {
440 for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
441 String templateName = getDefaultTemplateName(defTemplate);
442 InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
443 if (inputStream != null) {
444 readDefTemplateFile(inputStream, templateName);
448 } catch (Exception e) {
449 LOG.error(e);
453 public static String getDefaultTemplateName(String defTemplate) {
454 return defTemplate.substring(defTemplate.lastIndexOf("/") + 1);
457 public void readDefTemplateFile(InputStream inputStream, String defGroupName) throws JDOMException, InvalidDataException, IOException {
458 readTemplateFile(JDOMUtil.loadDocument(inputStream), defGroupName, true, true);
461 @Nullable
462 public TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate) throws InvalidDataException {
463 if (document == null) {
464 throw new InvalidDataException();
466 Element root = document.getRootElement();
467 if (root == null || !TEMPLATE_SET.equals(root.getName())) {
468 throw new InvalidDataException();
471 String groupName = root.getAttributeValue(GROUP);
472 if (groupName == null || groupName.length() == 0) groupName = defGroupName;
474 TemplateGroup result = new TemplateGroup(groupName);
476 Map<String, TemplateImpl> created = new LinkedHashMap<String, TemplateImpl>();
478 for (final Object o1 : root.getChildren(TEMPLATE)) {
479 Element element = (Element)o1;
481 TemplateImpl template = readTemplateFromElement(isDefault, groupName, element);
482 boolean doNotRegister = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) || myTemplates.containsKey(template.getKey()));
484 if(!doNotRegister) {
485 created.put(template.getKey(), template);
489 if (registerTemplate) {
490 TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
491 if (existingScheme != null) {
492 result = existingScheme;
496 for (TemplateImpl template : created.values()) {
497 if (registerTemplate) {
498 clearPreviouslyRegistered(template);
499 addTemplateImpl(template);
502 result.addElement(template);
505 if (registerTemplate) {
506 TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
507 if (existingScheme == null && !result.isEmpty()) {
508 mySchemesManager.addNewScheme(result, false);
512 return result.isEmpty() ? null : result;
516 private TemplateImpl readTemplateFromElement(final boolean isDefault, final String groupName, final Element element) throws
517 InvalidDataException {
518 String name = element.getAttributeValue(NAME);
519 String value = element.getAttributeValue(VALUE);
520 String description;
521 String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
522 String key = element.getAttributeValue(KEY);
523 String id = element.getAttributeValue(ID);
524 if (resourceBundle != null && key != null) {
525 ResourceBundle bundle = ResourceBundle.getBundle(resourceBundle);
526 description = bundle.getString(key);
528 else {
529 description = element.getAttributeValue(DESCRIPTION);
531 String shortcut = element.getAttributeValue(SHORTCUT);
532 TemplateImpl template = addTemplate(name, value, groupName, description, shortcut, isDefault, id);
534 template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
535 template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
536 template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
539 for (final Object o : element.getChildren(VARIABLE)) {
540 Element e = (Element)o;
541 String variableName = e.getAttributeValue(NAME);
542 String expression = e.getAttributeValue(EXPRESSION);
543 String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
544 boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
545 template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
548 Element context = element.getChild(CONTEXT);
549 if (context != null) {
550 template.getTemplateContext().readExternal(context);
553 return template;
556 public void readHiddenTemplateFile(Document document) throws InvalidDataException {
557 if (document == null) {
558 throw new InvalidDataException();
560 Element root = document.getRootElement();
561 if (root == null || !TEMPLATE_SET.equals(root.getName())) {
562 throw new InvalidDataException();
565 for (final Object o1 : root.getChildren(TEMPLATE)) {
567 addTemplateById(readTemplateFromElement(false, null, (Element)o1));
573 private static void saveTemplate(TemplateImpl template, Element templateSetElement) {
574 Element element = new Element(TEMPLATE);
575 final String id = template.getId();
576 if (id != null) {
577 element.setAttribute(ID, id);
579 element.setAttribute(NAME, template.getKey());
580 element.setAttribute(VALUE, template.getString());
581 if (template.getShortcutChar() == TAB_CHAR) {
582 element.setAttribute(SHORTCUT, TAB);
583 } else if (template.getShortcutChar() == ENTER_CHAR) {
584 element.setAttribute(SHORTCUT, ENTER);
585 } else if (template.getShortcutChar() == SPACE_CHAR) {
586 element.setAttribute(SHORTCUT, SPACE);
588 if (template.getDescription() != null) {
589 element.setAttribute(DESCRIPTION, template.getDescription());
591 element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
592 element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
593 if (template.isDeactivated()) {
594 element.setAttribute(DEACTIVATED, Boolean.toString(true));
597 for (int i = 0; i < template.getVariableCount(); i++) {
598 Element variableElement = new Element(VARIABLE);
599 variableElement.setAttribute(NAME, template.getVariableNameAt(i));
600 variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
601 variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
602 variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
603 element.addContent(variableElement);
606 try {
607 Element contextElement = new Element(CONTEXT);
608 template.getTemplateContext().writeExternal(contextElement);
609 element.addContent(contextElement);
610 } catch (WriteExternalException e) {
612 templateSetElement.addContent(element);
615 public void setTemplates(List<TemplateGroup> newGroups) {
616 myTemplates.clear();
617 myDeletedTemplates.clear();
618 for (TemplateImpl template : myDefaultTemplates.values()) {
619 myDeletedTemplates.add(TemplateKey.keyOf(template));
621 mySchemesManager.clearAllSchemes();
622 myMaxKeyLength = 0;
623 for (TemplateGroup group : newGroups) {
624 if (!group.isEmpty()) {
625 mySchemesManager.addNewScheme(group, true);
626 for (TemplateImpl template : group.getElements()) {
627 clearPreviouslyRegistered(template);
628 addTemplateImpl(template);
634 public SchemesManager<TemplateGroup,TemplateGroup> getSchemesManager() {
635 return mySchemesManager;
638 public List<TemplateGroup> getTemplateGroups() {
639 return mySchemesManager.getAllSchemes();
642 public List<TemplateImpl> collectMatchingCandidates(String key, char shortcutChar) {
643 final Collection<TemplateImpl> templates = getTemplates(key);
644 List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
645 for (TemplateImpl template : templates) {
646 if (template.isDeactivated()) {
647 continue;
649 if (getShortcutChar(template) != shortcutChar) {
650 continue;
652 if (template.isSelectionTemplate()) {
653 continue;
655 candidates.add(template);
657 return candidates;
660 private char getShortcutChar(TemplateImpl template) {
661 char c = template.getShortcutChar();
662 if (c == DEFAULT_CHAR) {
663 return getDefaultShortcutChar();
665 else {
666 return c;