f44c4b99c2d880e296990ef8913c757bbbc69b8b
[idea/community.git] / platform / projectModel-impl / src / com / intellij / application / options / PathMacrosImpl.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.application.options;
17
18 import com.intellij.openapi.application.PathMacros;
19 import com.intellij.openapi.components.*;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.util.containers.ContainerUtil;
23 import com.intellij.util.containers.hash.LinkedHashMap;
24 import gnu.trove.THashMap;
25 import gnu.trove.THashSet;
26 import org.jdom.Element;
27 import org.jetbrains.annotations.NonNls;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30 import org.jetbrains.jps.model.serialization.JpsGlobalLoader;
31 import org.jetbrains.jps.model.serialization.PathMacroUtil;
32
33 import java.util.Collection;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.concurrent.locks.ReentrantReadWriteLock;
38
39 @State(
40   name = "PathMacrosImpl",
41   storages = @Storage(value = "path.macros.xml", roamingType = RoamingType.PER_OS)
42 )
43 public class PathMacrosImpl extends PathMacros implements PersistentStateComponent<Element> {
44   private static final Logger LOG = Logger.getInstance(PathMacrosImpl.class);
45
46   private final Map<String, String> myLegacyMacros = new THashMap<>();
47   private final Map<String, String> myMacros = new LinkedHashMap<>();
48   private int myModificationStamp = 0;
49   private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock();
50   private final List<String> myIgnoredMacros = ContainerUtil.createLockFreeCopyOnWriteList();
51
52   private static final String MACRO_ELEMENT = JpsGlobalLoader.PathVariablesSerializer.MACRO_TAG;
53   private static final String NAME_ATTR = JpsGlobalLoader.PathVariablesSerializer.NAME_ATTRIBUTE;
54   private static final String VALUE_ATTR = JpsGlobalLoader.PathVariablesSerializer.VALUE_ATTRIBUTE;
55
56   @NonNls
57   public static final String IGNORED_MACRO_ELEMENT = "ignoredMacro";
58
59   // predefined macros
60   @NonNls
61   public static final String APPLICATION_HOME_MACRO_NAME = PathMacroUtil.APPLICATION_HOME_DIR;
62   @NonNls
63   public static final String PROJECT_DIR_MACRO_NAME = PathMacroUtil.PROJECT_DIR_MACRO_NAME;
64   @NonNls
65   public static final String MODULE_DIR_MACRO_NAME = PathMacroUtil.MODULE_DIR_MACRO_NAME;
66   @NonNls
67   public static final String USER_HOME_MACRO_NAME = PathMacroUtil.USER_HOME_NAME;
68
69   private static final Set<String> SYSTEM_MACROS = new THashSet<>();
70   @NonNls public static final String EXT_FILE_NAME = "path.macros";
71
72   static {
73     SYSTEM_MACROS.add(APPLICATION_HOME_MACRO_NAME);
74     SYSTEM_MACROS.add(PathMacroUtil.APPLICATION_PLUGINS_DIR);
75     SYSTEM_MACROS.add(PROJECT_DIR_MACRO_NAME);
76     SYSTEM_MACROS.add(MODULE_DIR_MACRO_NAME);
77     SYSTEM_MACROS.add(USER_HOME_MACRO_NAME);
78   }
79
80   @SuppressWarnings("SpellCheckingInspection")
81   private static final Set<String> ourToolsMacros = ContainerUtil.immutableSet(
82     "ClasspathEntry",
83     "Classpath",
84     "ColumnNumber",
85     "ContentRoot",
86     "FileClass",
87     "FileDir",
88     "FileParentDir",
89     "FileDirName",
90     "FileDirPathFromParent",
91     "FileDirRelativeToProjectRoot",
92     "/FileDirRelativeToProjectRoot",
93     "FileDirRelativeToSourcepath",
94     "/FileDirRelativeToSourcepath",
95     "FileExt",
96     "FileFQPackage",
97     "FileName",
98     "FileNameWithoutExtension",
99     "FileNameWithoutAllExtensions",
100     "FilePackage",
101     "FilePath",
102     "UnixSeparators",
103     "FilePathRelativeToProjectRoot",
104     "/FilePathRelativeToProjectRoot",
105     "FilePathRelativeToSourcepath",
106     "/FilePathRelativeToSourcepath",
107     "FilePrompt",
108     "FileRelativeDir",
109     "/FileRelativeDir",
110     "FileRelativePath",
111     "/FileRelativePath",
112     "FileEncoding",
113     "JavaDocPath",
114     "JDKPath",
115     "LineNumber",
116     "ModuleFileDir",
117     "ModuleFilePath",
118     "ModuleName",
119     "ModuleSourcePath",
120     "ModuleSdkPath",
121     "OutputPath",
122     "PhpExecutable",
123     "ProjectFileDir",
124     "ProjectFilePath",
125     "ProjectName",
126     "Projectpath",
127     "Prompt",
128     "SourcepathEntry",
129     "Sourcepath",
130     "SHOW_CHANGES",
131     "ClipboardContent",
132     "SelectedText",
133     "SelectionStartLine",
134     "SelectionEndLine",
135     "SelectionStartColumn",
136     "SelectionEndColumn",
137     "PyInterpreterDirectory",
138     "ExecutableByFileExt"
139   );
140
141   public PathMacrosImpl() {
142   }
143
144   public static PathMacrosImpl getInstanceEx() {
145     return (PathMacrosImpl)getInstance();
146   }
147
148   @Override
149   public Set<String> getUserMacroNames() {
150     myLock.readLock().lock();
151     try {
152       return new THashSet<>(myMacros.keySet()); // keyset should not escape the lock
153     }
154     finally {
155       myLock.readLock().unlock();
156     }
157   }
158
159   public static Set<String> getToolMacroNames() {
160     return ourToolsMacros;
161   }
162
163   @Override
164   public Set<String> getSystemMacroNames() {
165     return SYSTEM_MACROS;
166   }
167
168   @Override
169   public Collection<String> getIgnoredMacroNames() {
170     return myIgnoredMacros;
171   }
172
173   @Override
174   public void setIgnoredMacroNames(@NotNull final Collection<String> names) {
175     myIgnoredMacros.clear();
176     myIgnoredMacros.addAll(names);
177   }
178
179   @Override
180   public void addIgnoredMacro(@NotNull String name) {
181     if (!myIgnoredMacros.contains(name)) myIgnoredMacros.add(name);
182   }
183
184   public int getModificationStamp() {
185     myLock.readLock().lock();
186     try {
187       return myModificationStamp;
188     }
189     finally {
190       myLock.readLock().unlock();
191     }
192   }
193
194   @Override
195   public boolean isIgnoredMacroName(@NotNull String macro) {
196     return myIgnoredMacros.contains(macro);
197   }
198
199   @Override
200   public Set<String> getAllMacroNames() {
201     return ContainerUtil.union(getUserMacroNames(), getSystemMacroNames());
202   }
203
204   @Override
205   public String getValue(String name) {
206     try {
207       myLock.readLock().lock();
208       return myMacros.get(name);
209     }
210     finally {
211       myLock.readLock().unlock();
212     }
213   }
214
215   @Override
216   public void removeAllMacros() {
217     try {
218       myLock.writeLock().lock();
219       myMacros.clear();
220     }
221     finally {
222       myModificationStamp++;
223       myLock.writeLock().unlock();
224     }
225   }
226
227   @Override
228   public Collection<String> getLegacyMacroNames() {
229     try {
230       myLock.readLock().lock();
231       // keyset should not escape the lock
232       return new THashSet<>(myLegacyMacros.keySet());
233     }
234     finally {
235       myLock.readLock().unlock();
236     }
237   }
238
239   @Override
240   public void setMacro(@NotNull String name, @NotNull String value) {
241     if (StringUtil.isEmptyOrSpaces(value)) {
242       return;
243     }
244
245     try {
246       myLock.writeLock().lock();
247       myMacros.put(name, value);
248     }
249     finally {
250       myModificationStamp++;
251       myLock.writeLock().unlock();
252     }
253   }
254
255   @Override
256   public void addLegacyMacro(@NotNull String name, @NotNull String value) {
257     try {
258       myLock.writeLock().lock();
259       myLegacyMacros.put(name, value);
260       myMacros.remove(name);
261     }
262     finally {
263       myModificationStamp++;
264       myLock.writeLock().unlock();
265     }
266   }
267
268   @Override
269   public void removeMacro(String name) {
270     try {
271       myLock.writeLock().lock();
272       final String value = myMacros.remove(name);
273       LOG.assertTrue(value != null);
274     }
275     finally {
276       myModificationStamp++;
277       myLock.writeLock().unlock();
278     }
279   }
280
281   @Nullable
282   @Override
283   public Element getState() {
284     try {
285       Element element = new Element("state");
286       myLock.writeLock().lock();
287
288       for (Map.Entry<String, String> entry : myMacros.entrySet()) {
289         String value = entry.getValue();
290         if (!StringUtil.isEmptyOrSpaces(value)) {
291           final Element macro = new Element(MACRO_ELEMENT);
292           macro.setAttribute(NAME_ATTR, entry.getKey());
293           macro.setAttribute(VALUE_ATTR, value);
294           element.addContent(macro);
295         }
296       }
297
298       for (final String macro : myIgnoredMacros) {
299         final Element macroElement = new Element(IGNORED_MACRO_ELEMENT);
300         macroElement.setAttribute(NAME_ATTR, macro);
301         element.addContent(macroElement);
302       }
303       return element;
304     }
305     finally {
306       myLock.writeLock().unlock();
307     }
308   }
309
310   @Override
311   public void loadState(Element element) {
312     try {
313       myLock.writeLock().lock();
314
315       for (Element macro : element.getChildren(MACRO_ELEMENT)) {
316         final String name = macro.getAttributeValue(NAME_ATTR);
317         String value = macro.getAttributeValue(VALUE_ATTR);
318         if (name == null || value == null) {
319           continue;
320         }
321
322         if (SYSTEM_MACROS.contains(name)) {
323           continue;
324         }
325
326         if (value.length() > 1 && value.charAt(value.length() - 1) == '/') {
327           value = value.substring(0, value.length() - 1);
328         }
329
330         myMacros.put(name, value);
331       }
332
333       for (Element macroElement : element.getChildren(IGNORED_MACRO_ELEMENT)) {
334         String ignoredName = macroElement.getAttributeValue(NAME_ATTR);
335         if (!StringUtil.isEmpty(ignoredName) && !myIgnoredMacros.contains(ignoredName)) {
336           myIgnoredMacros.add(ignoredName);
337         }
338       }
339     }
340     finally {
341       myModificationStamp++;
342       myLock.writeLock().unlock();
343     }
344   }
345
346   public void addMacroReplacements(ReplacePathToMacroMap result) {
347     for (String name : getUserMacroNames()) {
348       String value = getValue(name);
349       if (!StringUtil.isEmptyOrSpaces(value)) {
350         result.addMacroReplacement(value, name);
351       }
352     }
353   }
354
355   public void addMacroExpands(ExpandMacroToPathMap result) {
356     for (String name : getUserMacroNames()) {
357       String value = getValue(name);
358       if (!StringUtil.isEmptyOrSpaces(value)) {
359         result.addMacroExpand(name, value);
360       }
361     }
362
363     myLock.readLock().lock();
364     try {
365       for (Map.Entry<String, String> entry : myLegacyMacros.entrySet()) {
366         result.addMacroExpand(entry.getKey(), entry.getValue());
367       }
368     }
369     finally {
370       myLock.readLock().unlock();
371     }
372   }
373 }