diff options
Diffstat (limited to 'platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java')
-rw-r--r-- | platform/projectModel-impl/src/com/intellij/lang/LanguagePerFileMappings.java | 281 |
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(); + } + } + +} |