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
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
48 name
="TemplateSettings",
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
;
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
;
117 private TemplateKey(String groupName
, String key
) {
118 this.groupName
= groupName
;
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
)) {
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
);
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
);
196 public File
[] getExportFiles() {
197 return new File
[]{getTemplateDirectory(true),PathManager
.getDefaultOptionsFile()};
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
;
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
);
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
);
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
);
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
)) {
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
);
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());
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();
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
);
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
,
389 TemplateImpl template
= new TemplateImpl(key
, string
, group
);
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
);
399 template
.setShortcutChar(DEFAULT_CHAR
);
402 myDefaultTemplates
.put(key
, template
);
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()) {
415 if (!directory
.mkdir()) {
416 if (LOG
.isDebugEnabled()) {
417 LOG
.debug("cannot create directory: " + directory
.getAbsolutePath());
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
);
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
) {
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);
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()));
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
);
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
);
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
);
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();
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
);
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
) {
617 myDeletedTemplates
.clear();
618 for (TemplateImpl template
: myDefaultTemplates
.values()) {
619 myDeletedTemplates
.add(TemplateKey
.keyOf(template
));
621 mySchemesManager
.clearAllSchemes();
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()) {
649 if (getShortcutChar(template
) != shortcutChar
) {
652 if (template
.isSelectionTemplate()) {
655 candidates
.add(template
);
660 private char getShortcutChar(TemplateImpl template
) {
661 char c
= template
.getShortcutChar();
662 if (c
== DEFAULT_CHAR
) {
663 return getDefaultShortcutChar();