summaryrefslogtreecommitdiff
path: root/platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java')
-rw-r--r--platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java281
1 files changed, 281 insertions, 0 deletions
diff --git a/platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java b/platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java
new file mode 100644
index 000000000000..c1a186b614c7
--- /dev/null
+++ b/platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2000-2009 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.lang;
+
+import com.intellij.injected.editor.VirtualFileWindow;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.impl.FilePropertyPusher;
+import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.testFramework.LightVirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashMap;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import java.util.*;
+
+/**
+ * @author peter
+ */
+public abstract class LanguagePerFileMappings<T> implements PersistentStateComponent<Element>, PerFileMappings<T> {
+
+ private static final Logger LOG = Logger.getInstance("com.intellij.lang.LanguagePerFileMappings");
+
+ private final Map<VirtualFile, T> myMappings = new HashMap<VirtualFile, T>();
+ private final Project myProject;
+
+ public LanguagePerFileMappings(final Project project) {
+ myProject = project;
+ }
+
+ @Nullable
+ protected FilePropertyPusher<T> getFilePropertyPusher() {
+ return null;
+ }
+
+ @Override
+ public Map<VirtualFile, T> getMappings() {
+ synchronized (myMappings) {
+ cleanup();
+ return Collections.unmodifiableMap(myMappings);
+ }
+ }
+
+ private void cleanup() {
+ for (final VirtualFile file : new ArrayList<VirtualFile>(myMappings.keySet())) {
+ if (file != null //PROJECT, top-level
+ && !file.isValid()) {
+ myMappings.remove(file);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public T getMapping(@Nullable VirtualFile file) {
+ FilePropertyPusher<T> pusher = getFilePropertyPusher();
+ T t = getMappingInner(file, myMappings, pusher == null? null : pusher.getFileDataKey());
+ return t == null? getDefaultMapping(file) : t;
+ }
+
+ @Nullable
+ protected static <T> T getMappingInner(@Nullable VirtualFile file, @Nullable Map<VirtualFile, T> mappings, @Nullable Key<T> pusherKey) {
+ if (file instanceof VirtualFileWindow) {
+ final VirtualFileWindow window = (VirtualFileWindow)file;
+ file = window.getDelegate();
+ }
+ VirtualFile originalFile = file instanceof LightVirtualFile ? ((LightVirtualFile)file).getOriginalFile() : null;
+ if (Comparing.equal(originalFile, file)) originalFile = null;
+
+ if (file != null) {
+ final T pushedValue = pusherKey == null? null : file.getUserData(pusherKey);
+ if (pushedValue != null) return pushedValue;
+ }
+ if (originalFile != null) {
+ final T pushedValue = pusherKey == null? null : originalFile.getUserData(pusherKey);
+ if (pushedValue != null) return pushedValue;
+ }
+ if (mappings == null) return null;
+ synchronized (mappings) {
+ for (VirtualFile cur = file; ; cur = cur.getParent()) {
+ T t = mappings.get(cur);
+ if (t != null) return t;
+ if (originalFile != null) {
+ t = mappings.get(originalFile);
+ if (t != null) return t;
+ originalFile = originalFile.getParent();
+ }
+ if (cur == null) break;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public T chosenToStored(VirtualFile file, T value) {
+ return value;
+ }
+
+ @Override
+ public boolean isSelectable(T value) {
+ return true;
+ }
+
+ @Override
+ @Nullable
+ public T getDefaultMapping(@Nullable final VirtualFile file) {
+ return null;
+ }
+
+ @Nullable
+ public T getImmediateMapping(@Nullable final VirtualFile file) {
+ synchronized (myMappings) {
+ return myMappings.get(file);
+ }
+ }
+
+ @Override
+ public void setMappings(final Map<VirtualFile, T> mappings) {
+ final Collection<VirtualFile> oldFiles;
+ synchronized (myMappings) {
+ oldFiles = new ArrayList<VirtualFile>(myMappings.keySet());
+ myMappings.clear();
+ myMappings.putAll(mappings);
+ cleanup();
+ }
+ handleMappingChange(mappings.keySet(), oldFiles, !getProject().isDefault());
+ }
+
+ public void setMapping(@Nullable final VirtualFile file, @Nullable T dialect) {
+ synchronized (myMappings) {
+ if (dialect == null) {
+ myMappings.remove(file);
+ }
+ else {
+ myMappings.put(file, dialect);
+ }
+ }
+ final List<VirtualFile> files = ContainerUtil.createMaybeSingletonList(file);
+ handleMappingChange(files, files, false);
+ }
+
+ private void handleMappingChange(final Collection<VirtualFile> files, Collection<VirtualFile> oldFiles, final boolean includeOpenFiles) {
+ final FilePropertyPusher<T> pusher = getFilePropertyPusher();
+ if (pusher != null) {
+ for (VirtualFile oldFile : oldFiles) {
+ if (oldFile == null) continue; // project
+ oldFile.putUserData(pusher.getFileDataKey(), null);
+ }
+ PushedFilePropertiesUpdater updater = PushedFilePropertiesUpdater.getInstance(myProject);
+ if (updater == null) {
+ if (!myProject.isDefault()) {
+ LOG.error("updater = null. project=" + myProject.getName()+", this="+getClass().getSimpleName());
+ }
+ }
+ else {
+ updater.pushAll(pusher);
+ }
+ }
+ if (shouldReparseFiles()) {
+ PsiDocumentManager.getInstance(myProject).reparseFiles(files, includeOpenFiles);
+ }
+ }
+
+ @Override
+ public Collection<T> getAvailableValues(VirtualFile file) {
+ return getAvailableValues();
+ }
+
+ protected abstract List<T> getAvailableValues();
+
+ @Nullable
+ protected abstract String serialize(T t);
+
+ @Override
+ public Element getState() {
+ synchronized (myMappings) {
+ cleanup();
+ final Element element = new Element("x");
+ final List<VirtualFile> files = new ArrayList<VirtualFile>(myMappings.keySet());
+ Collections.sort(files, new Comparator<VirtualFile>() {
+ @Override
+ public int compare(final VirtualFile o1, final VirtualFile o2) {
+ if (o1 == null || o2 == null) return o1 == null ? o2 == null ? 0 : 1 : -1;
+ return o1.getPath().compareTo(o2.getPath());
+ }
+ });
+ for (VirtualFile file : files) {
+ final T dialect = myMappings.get(file);
+ String value = serialize(dialect);
+ if (value != null) {
+ final Element child = new Element("file");
+ element.addContent(child);
+ child.setAttribute("url", file == null ? "PROJECT" : file.getUrl());
+ child.setAttribute(getValueAttribute(), value);
+ }
+ }
+ return element;
+ }
+ }
+
+ @Nullable
+ protected T handleUnknownMapping(VirtualFile file, String value) {
+ return null;
+ }
+
+ @NotNull
+ protected String getValueAttribute() {
+ return "dialect";
+ }
+
+ @Override
+ public void loadState(final Element state) {
+ synchronized (myMappings) {
+ final THashMap<String, T> dialectMap = new THashMap<String, T>();
+ for (T dialect : getAvailableValues()) {
+ String key = serialize(dialect);
+ if (key != null) {
+ dialectMap.put(key, dialect);
+ }
+ }
+ final List<Element> files = state.getChildren("file");
+ for (Element fileElement : files) {
+ final String url = fileElement.getAttributeValue("url");
+ final String dialectID = fileElement.getAttributeValue(getValueAttribute());
+ final VirtualFile file = url.equals("PROJECT") ? null : VirtualFileManager.getInstance().findFileByUrl(url);
+ T dialect = dialectMap.get(dialectID);
+ if (dialect == null) {
+ dialect = handleUnknownMapping(file, dialectID);
+ if (dialect == null) continue;
+ }
+ if (file != null || url.equals("PROJECT")) {
+ myMappings.put(file, dialect);
+ }
+ }
+ }
+ }
+
+ @TestOnly
+ public void cleanupForNextTest() {
+ synchronized (myMappings) {
+ myMappings.clear();
+ }
+ }
+
+ protected Project getProject() {
+ return myProject;
+ }
+
+ protected boolean shouldReparseFiles() {
+ return true;
+ }
+
+ public boolean hasMappings() {
+ synchronized (myMappings) {
+ return !myMappings.isEmpty();
+ }
+ }
+
+}