/* * Copyright 2000-2016 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.psi.codeStyle; import com.intellij.lang.Language; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import org.jdom.Content; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; /** * Manages common code style settings for every language using them. * * @author Rustam Vishnyakov */ public class CommonCodeStyleSettingsManager { private volatile Map<Language, CommonCodeStyleSettings> myCommonSettingsMap; private volatile Map<String, Content> myUnknownSettingsMap; @NotNull private final CodeStyleSettings myParentSettings; @NonNls static final String COMMON_SETTINGS_TAG = "codeStyleSettings"; private static final String LANGUAGE_ATTR = "language"; CommonCodeStyleSettingsManager(@NotNull CodeStyleSettings parentSettings) { myParentSettings = parentSettings; } /** * Attempts to get language-specific common settings from {@code LanguageCodeStyleSettingsProvider}. * * @param lang The language to get settings for. * @return If the provider for the language exists and is able to create language-specific default settings * ({@code LanguageCodeStyleSettingsProvider.getDefaultCommonSettings()} doesn't return null) * returns the instance of settings for this language. Otherwise returns the instance of parent settings * shared between several languages. */ public CommonCodeStyleSettings getCommonSettings(@Nullable Language lang) { Map<Language, CommonCodeStyleSettings> commonSettingsMap = getCommonSettingsMap(); CommonCodeStyleSettings settings = commonSettingsMap.get(lang); while (settings == null && lang != null) { lang = lang.getBaseLanguage(); settings = commonSettingsMap.get(lang); } if (settings != null) { return settings; } return myParentSettings; } @NotNull private Map<Language, CommonCodeStyleSettings> getCommonSettingsMap() { Map<Language, CommonCodeStyleSettings> commonSettingsMap = myCommonSettingsMap; if (commonSettingsMap == null) { synchronized (this) { commonSettingsMap = myCommonSettingsMap; if (commonSettingsMap == null) { commonSettingsMap = initCommonSettingsMap(); initNonReadSettings(); } } } return commonSettingsMap; } /** * Get common code style settings by language name. {@code getCommonSettings(Language)} is a preferred method but * sometimes (for example, in plug-ins which do not depend on a specific language support) language settings can be * obtained by name. * * @param langName The display name of the language whose settings must be returned. * @return Common code style settings for the given language or parent (shared) settings if not found. */ @NotNull public CommonCodeStyleSettings getCommonSettings(@NotNull String langName) { Map<Language, CommonCodeStyleSettings> map = getCommonSettingsMap(); for (Map.Entry<Language, CommonCodeStyleSettings> entry : map.entrySet()) { if (langName.equals(entry.getKey().getDisplayName())) { return entry.getValue(); } } return myParentSettings; } private void initNonReadSettings() { final LanguageCodeStyleSettingsProvider[] providers = Extensions.getExtensions(LanguageCodeStyleSettingsProvider.EP_NAME); for (final LanguageCodeStyleSettingsProvider provider : providers) { Language target = provider.getLanguage(); if (!myCommonSettingsMap.containsKey(target)) { CommonCodeStyleSettings initialSettings = provider.getDefaultCommonSettings(); if (initialSettings != null) { init(initialSettings, target); } } } } private void init(@NotNull CommonCodeStyleSettings initialSettings, @NotNull Language target) { initialSettings.setRootSettings(myParentSettings); registerCommonSettings(target, initialSettings); } private Map<Language, CommonCodeStyleSettings> initCommonSettingsMap() { Map<Language, CommonCodeStyleSettings> map = new LinkedHashMap<>(); myCommonSettingsMap = map; myUnknownSettingsMap = new LinkedHashMap<>(); return map; } private void registerCommonSettings(@NotNull Language lang, @NotNull CommonCodeStyleSettings settings) { synchronized (this) { if (!myCommonSettingsMap.containsKey(lang)) { myCommonSettingsMap.put(lang, settings); settings.getRootSettings(); // check not null } } } @NotNull public CommonCodeStyleSettingsManager clone(@NotNull CodeStyleSettings parentSettings) { synchronized (this) { CommonCodeStyleSettingsManager settingsManager = new CommonCodeStyleSettingsManager(parentSettings); if (myCommonSettingsMap != null && !myCommonSettingsMap.isEmpty()) { settingsManager.initCommonSettingsMap(); for (Map.Entry<Language, CommonCodeStyleSettings> entry : myCommonSettingsMap.entrySet()) { CommonCodeStyleSettings clonedSettings = entry.getValue().clone(parentSettings); settingsManager.registerCommonSettings(entry.getKey(), clonedSettings); } } return settingsManager; } } public void readExternal(@NotNull Element element) throws InvalidDataException { synchronized (this) { initCommonSettingsMap(); for (Element commonSettingsElement : element.getChildren(COMMON_SETTINGS_TAG)) { final String languageId = commonSettingsElement.getAttributeValue(LANGUAGE_ATTR); if (!StringUtil.isEmpty(languageId)) { Language target = Language.findLanguageByID(languageId); boolean isKnownLanguage = target != null; if (isKnownLanguage) { final LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(target); if (provider != null) { CommonCodeStyleSettings settings = provider.getDefaultCommonSettings(); if (settings != null) { settings.readExternal(commonSettingsElement); init(settings, target); } } else { isKnownLanguage = false; } } if (!isKnownLanguage) { myUnknownSettingsMap.put(languageId, commonSettingsElement.clone()); } } } initNonReadSettings(); } } public void writeExternal(@NotNull Element element) throws WriteExternalException { synchronized (this) { if (myCommonSettingsMap == null) { return; } final Map<String, Language> idToLang = new THashMap<>(); for (Language language : myCommonSettingsMap.keySet()) { idToLang.put(language.getID(), language); } String[] languages = ArrayUtil.toStringArray(ContainerUtil.union(myUnknownSettingsMap.keySet(), idToLang.keySet())); Arrays.sort(languages); for (String id : languages) { final Language language = idToLang.get(id); if (language != null) { final CommonCodeStyleSettings commonSettings = myCommonSettingsMap.get(language); Element commonSettingsElement = new Element(COMMON_SETTINGS_TAG); commonSettings.writeExternal(commonSettingsElement); commonSettingsElement.setAttribute(LANGUAGE_ATTR, language.getID()); if (!commonSettingsElement.getChildren().isEmpty()) { element.addContent(commonSettingsElement); } } else { final Content unknown = myUnknownSettingsMap.get(id); if (unknown != null) { element.addContent(unknown.detach()); } } } } } public static void copy(@NotNull CommonCodeStyleSettings source, @NotNull CommonCodeStyleSettings target) { CommonCodeStyleSettings.copyPublicFields(source, target); CommonCodeStyleSettings.IndentOptions targetIndentOptions = target.getIndentOptions(); if (targetIndentOptions != null) { CommonCodeStyleSettings.IndentOptions sourceIndentOptions = source.getIndentOptions(); if (sourceIndentOptions != null) { CommonCodeStyleSettings.copyPublicFields(sourceIndentOptions, targetIndentOptions); } } } @Override public boolean equals(Object obj) { if (obj instanceof CommonCodeStyleSettingsManager) { CommonCodeStyleSettingsManager other = (CommonCodeStyleSettingsManager)obj; if (getCommonSettingsMap().size() != other.getCommonSettingsMap().size() || myUnknownSettingsMap.size() != other.myUnknownSettingsMap.size()) { return false; } for (Language language : myCommonSettingsMap.keySet()) { CommonCodeStyleSettings theseSettings = myCommonSettingsMap.get(language); CommonCodeStyleSettings otherSettings = other.getCommonSettings(language); if (!theseSettings.equals(otherSettings)) return false; } } return true; } }