/* * 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.application.options; import com.intellij.openapi.application.PathMacros; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.hash.LinkedHashMap; import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.serialization.JpsGlobalLoader; import org.jetbrains.jps.model.serialization.PathMacroUtil; import java.util.*; import java.util.concurrent.locks.ReentrantReadWriteLock; @State( name = "PathMacrosImpl", storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/path.macros.xml", roamingType = RoamingType.PER_PLATFORM)} ) public class PathMacrosImpl extends PathMacros implements PersistentStateComponent { private static final Logger LOG = Logger.getInstance(PathMacrosImpl.class); private final Map myLegacyMacros = new HashMap(); private final Map myMacros = new LinkedHashMap(); private int myModificationStamp = 0; private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock(); private final List myIgnoredMacros = ContainerUtil.createLockFreeCopyOnWriteList(); private static final String MACRO_ELEMENT = JpsGlobalLoader.PathVariablesSerializer.MACRO_TAG; private static final String NAME_ATTR = JpsGlobalLoader.PathVariablesSerializer.NAME_ATTRIBUTE; private static final String VALUE_ATTR = JpsGlobalLoader.PathVariablesSerializer.VALUE_ATTRIBUTE; @NonNls public static final String IGNORED_MACRO_ELEMENT = "ignoredMacro"; // predefined macros @NonNls public static final String APPLICATION_HOME_MACRO_NAME = PathMacroUtil.APPLICATION_HOME_DIR; @NonNls public static final String PROJECT_DIR_MACRO_NAME = PathMacroUtil.PROJECT_DIR_MACRO_NAME; @NonNls public static final String MODULE_DIR_MACRO_NAME = PathMacroUtil.MODULE_DIR_MACRO_NAME; @NonNls public static final String USER_HOME_MACRO_NAME = PathMacroUtil.USER_HOME_NAME; private static final Set SYSTEM_MACROS = new HashSet(); @NonNls public static final String EXT_FILE_NAME = "path.macros"; static { SYSTEM_MACROS.add(APPLICATION_HOME_MACRO_NAME); SYSTEM_MACROS.add(PathMacroUtil.APPLICATION_PLUGINS_DIR); SYSTEM_MACROS.add(PROJECT_DIR_MACRO_NAME); SYSTEM_MACROS.add(MODULE_DIR_MACRO_NAME); SYSTEM_MACROS.add(USER_HOME_MACRO_NAME); } @SuppressWarnings("SpellCheckingInspection") private static final Set ourToolsMacros = ContainerUtil.immutableSet( "ClasspathEntry", "Classpath", "ColumnNumber", "FileClass", "FileDir", "FileParentDir", "FileDirName", "FileDirPathFromParent", "FileDirRelativeToProjectRoot", "/FileDirRelativeToProjectRoot", "FileDirRelativeToSourcepath", "/FileDirRelativeToSourcepath", "FileExt", "FileFQPackage", "FileName", "FileNameWithoutExtension", "FileNameWithoutAllExtensions", "FilePackage", "FilePath", "UnixSeparators", "FilePathRelativeToProjectRoot", "/FilePathRelativeToProjectRoot", "FilePathRelativeToSourcepath", "/FilePathRelativeToSourcepath", "FilePrompt", "FileRelativeDir", "/FileRelativeDir", "FileRelativePath", "/FileRelativePath", "FileEncoding", "JavaDocPath", "JDKPath", "LineNumber", "ModuleFileDir", "ModuleFilePath", "ModuleName", "ModuleSourcePath", "ModuleSdkPath", "OutputPath", "PhpExecutable", "ProjectFileDir", "ProjectFilePath", "ProjectName", "Projectpath", "Prompt", "SourcepathEntry", "Sourcepath", "SHOW_CHANGES", "ClipboardContent", "SelectedText", "SelectionStartLine", "SelectionEndLine", "SelectionStartColumn", "SelectionEndColumn", "PyInterpreterDirectory" ); public PathMacrosImpl() { //setMacro(USER_HOME_MACRO_NAME, FileUtil.toSystemIndependentName(SystemProperties.getUserHome())); } public static PathMacrosImpl getInstanceEx() { return (PathMacrosImpl)getInstance(); } @Override public Set getUserMacroNames() { myLock.readLock().lock(); try { return new THashSet(myMacros.keySet()); // keyset should not escape the lock } finally { myLock.readLock().unlock(); } } public static Set getToolMacroNames() { return ourToolsMacros; } @Override public Set getSystemMacroNames() { return SYSTEM_MACROS; } @Override public Collection getIgnoredMacroNames() { return myIgnoredMacros; } @Override public void setIgnoredMacroNames(@NotNull final Collection names) { myIgnoredMacros.clear(); myIgnoredMacros.addAll(names); } @Override public void addIgnoredMacro(@NotNull String name) { if (!myIgnoredMacros.contains(name)) myIgnoredMacros.add(name); } public int getModificationStamp() { myLock.readLock().lock(); try { return myModificationStamp; } finally { myLock.readLock().unlock(); } } @Override public boolean isIgnoredMacroName(@NotNull String macro) { return myIgnoredMacros.contains(macro); } @Override public Set getAllMacroNames() { final Set userMacroNames = getUserMacroNames(); final Set systemMacroNames = getSystemMacroNames(); final Set allNames = new HashSet(userMacroNames.size() + systemMacroNames.size()); allNames.addAll(systemMacroNames); allNames.addAll(userMacroNames); return allNames; } @Override public String getValue(String name) { try { myLock.readLock().lock(); return myMacros.get(name); } finally { myLock.readLock().unlock(); } } @Override public void removeAllMacros() { try { myLock.writeLock().lock(); myMacros.clear(); } finally { myModificationStamp++; myLock.writeLock().unlock(); } } @Override public Collection getLegacyMacroNames() { try { myLock.readLock().lock(); return new THashSet(myLegacyMacros.keySet()); // keyset should not escape the lock } finally { myLock.readLock().unlock(); } } @Override public void setMacro(@NotNull String name, @NotNull String value) { if (value.trim().isEmpty()) return; try { myLock.writeLock().lock(); myMacros.put(name, value); } finally { myModificationStamp++; myLock.writeLock().unlock(); } } @Override public void addLegacyMacro(@NotNull String name, @NotNull String value) { try { myLock.writeLock().lock(); myLegacyMacros.put(name, value); myMacros.remove(name); } finally { myModificationStamp++; myLock.writeLock().unlock(); } } @Override public void removeMacro(String name) { try { myLock.writeLock().lock(); final String value = myMacros.remove(name); LOG.assertTrue(value != null); } finally { myModificationStamp++; myLock.writeLock().unlock(); } } @Nullable @Override public Element getState() { try { Element element = new Element("state"); myLock.writeLock().lock(); for (Map.Entry entry : myMacros.entrySet()) { String value = entry.getValue(); if (!StringUtil.isEmptyOrSpaces(value)) { final Element macro = new Element(MACRO_ELEMENT); macro.setAttribute(NAME_ATTR, entry.getKey()); macro.setAttribute(VALUE_ATTR, value); element.addContent(macro); } } for (final String macro : myIgnoredMacros) { final Element macroElement = new Element(IGNORED_MACRO_ELEMENT); macroElement.setAttribute(NAME_ATTR, macro); element.addContent(macroElement); } return element; } finally { myLock.writeLock().unlock(); } } @Override public void loadState(Element element) { try { myLock.writeLock().lock(); final List children = element.getChildren(MACRO_ELEMENT); for (Object aChildren : children) { Element macro = (Element)aChildren; final String name = macro.getAttributeValue(NAME_ATTR); String value = macro.getAttributeValue(VALUE_ATTR); if (name == null || value == null) { continue; } if (SYSTEM_MACROS.contains(name)) { continue; } if (value.length() > 1 && value.charAt(value.length() - 1) == '/') { value = value.substring(0, value.length() - 1); } myMacros.put(name, value); } final List ignoredChildren = element.getChildren(IGNORED_MACRO_ELEMENT); for (final Object child : ignoredChildren) { final Element macroElement = (Element)child; final String ignoredName = macroElement.getAttributeValue(NAME_ATTR); if (ignoredName != null && !ignoredName.isEmpty() && !myIgnoredMacros.contains(ignoredName)) { myIgnoredMacros.add(ignoredName); } } } finally { myModificationStamp++; myLock.writeLock().unlock(); } } public void addMacroReplacements(ReplacePathToMacroMap result) { for (final String name : getUserMacroNames()) { final String value = getValue(name); if (value != null && !value.trim().isEmpty()) result.addMacroReplacement(value, name); } } public void addMacroExpands(ExpandMacroToPathMap result) { for (final String name : getUserMacroNames()) { final String value = getValue(name); if (value != null && !value.trim().isEmpty()) result.addMacroExpand(name, value); } myLock.readLock().lock(); try { for (Map.Entry entry : myLegacyMacros.entrySet()) { result.addMacroExpand(entry.getKey(), entry.getValue()); } } finally { myLock.readLock().unlock(); } } }