/* * Copyright 2000-2015 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.openapi.fileTypes.impl; import com.intellij.openapi.fileTypes.ExactFileNameMatcher; import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher; import com.intellij.openapi.fileTypes.FileNameMatcher; import com.intellij.openapi.fileTypes.FileNameMatcherEx; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.util.ArrayUtil; import com.intellij.util.text.CharSequenceHashingStrategy; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * @author max */ public class FileTypeAssocTable<T> { private final Map<CharSequence, T> myExtensionMappings; private final Map<CharSequence, T> myExactFileNameMappings; private final Map<CharSequence, T> myExactFileNameAnyCaseMappings; private final List<Pair<FileNameMatcher, T>> myMatchingMappings; private FileTypeAssocTable(@NotNull Map<CharSequence, T> extensionMappings, @NotNull Map<CharSequence, T> exactFileNameMappings, @NotNull Map<CharSequence, T> exactFileNameAnyCaseMappings, @NotNull List<Pair<FileNameMatcher, T>> matchingMappings) { myExtensionMappings = new THashMap<>(Math.max(10, extensionMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_INSENSITIVE); myExtensionMappings.putAll(extensionMappings); myExactFileNameMappings = new THashMap<>(Math.max(10, exactFileNameMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_SENSITIVE); myExactFileNameMappings.putAll(exactFileNameMappings); myExactFileNameAnyCaseMappings = new THashMap<>(Math.max(10, exactFileNameAnyCaseMappings.size()), 0.5f, CharSequenceHashingStrategy.CASE_INSENSITIVE); myExactFileNameAnyCaseMappings.putAll(exactFileNameAnyCaseMappings); myMatchingMappings = new ArrayList<>(matchingMappings); } public FileTypeAssocTable() { this(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList()); } boolean isAssociatedWith(@NotNull T type, @NotNull FileNameMatcher matcher) { if (matcher instanceof ExtensionFileNameMatcher || matcher instanceof ExactFileNameMatcher) { return findAssociatedFileType(matcher) == type; } for (Pair<FileNameMatcher, T> mapping : myMatchingMappings) { if (matcher.equals(mapping.getFirst()) && type == mapping.getSecond()) return true; } return false; } public void addAssociation(@NotNull FileNameMatcher matcher, @NotNull T type) { if (matcher instanceof ExtensionFileNameMatcher) { myExtensionMappings.put(((ExtensionFileNameMatcher)matcher).getExtension(), type); } else if (matcher instanceof ExactFileNameMatcher) { final ExactFileNameMatcher exactFileNameMatcher = (ExactFileNameMatcher)matcher; Map<CharSequence, T> mapToUse = exactFileNameMatcher.isIgnoreCase() ? myExactFileNameAnyCaseMappings : myExactFileNameMappings; mapToUse.put(exactFileNameMatcher.getFileName(), type); } else { myMatchingMappings.add(Pair.create(matcher, type)); } } boolean removeAssociation(@NotNull FileNameMatcher matcher, @NotNull T type) { if (matcher instanceof ExtensionFileNameMatcher) { String extension = ((ExtensionFileNameMatcher)matcher).getExtension(); if (myExtensionMappings.get(extension) == type) { myExtensionMappings.remove(extension); return true; } return false; } if (matcher instanceof ExactFileNameMatcher) { final ExactFileNameMatcher exactFileNameMatcher = (ExactFileNameMatcher)matcher; String fileName = exactFileNameMatcher.getFileName(); final Map<CharSequence, T> mapToUse = exactFileNameMatcher.isIgnoreCase() ? myExactFileNameAnyCaseMappings : myExactFileNameMappings; if(mapToUse.get(fileName) == type) { mapToUse.remove(fileName); return true; } return false; } List<Pair<FileNameMatcher, T>> copy = new ArrayList<>(myMatchingMappings); for (Pair<FileNameMatcher, T> assoc : copy) { if (matcher.equals(assoc.getFirst())) { myMatchingMappings.remove(assoc); return true; } } return false; } boolean removeAllAssociations(@NotNull T type) { boolean changed = removeAssociationsFromMap(myExtensionMappings, type, false); changed = removeAssociationsFromMap(myExactFileNameAnyCaseMappings, type, changed); changed = removeAssociationsFromMap(myExactFileNameMappings, type, changed); List<Pair<FileNameMatcher, T>> copy = new ArrayList<>(myMatchingMappings); for (Pair<FileNameMatcher, T> assoc : copy) { if (assoc.getSecond() == type) { myMatchingMappings.remove(assoc); changed = true; } } return changed; } private boolean removeAssociationsFromMap(@NotNull Map<CharSequence, T> extensionMappings, @NotNull T type, boolean changed) { Set<CharSequence> exts = extensionMappings.keySet(); CharSequence[] extsStrings = exts.toArray(new CharSequence[exts.size()]); for (CharSequence s : extsStrings) { if (extensionMappings.get(s) == type) { extensionMappings.remove(s); changed = true; } } return changed; } @Nullable public T findAssociatedFileType(@NotNull @NonNls CharSequence fileName) { if (!myExactFileNameMappings.isEmpty()) { T t = myExactFileNameMappings.get(fileName); if (t != null) return t; } if (!myExactFileNameAnyCaseMappings.isEmpty()) { // even hash lookup with case insensitive hasher is costly for isIgnored checks during compile T t = myExactFileNameAnyCaseMappings.get(fileName); if (t != null) return t; } //noinspection ForLoopReplaceableByForEach for (int i = 0; i < myMatchingMappings.size(); i++) { final Pair<FileNameMatcher, T> mapping = myMatchingMappings.get(i); if (FileNameMatcherEx.acceptsCharSequence(mapping.getFirst(), fileName)) return mapping.getSecond(); } return myExtensionMappings.get(FileUtilRt.getExtension(fileName)); } @Nullable T findAssociatedFileType(@NotNull FileNameMatcher matcher) { if (matcher instanceof ExtensionFileNameMatcher) { return myExtensionMappings.get(((ExtensionFileNameMatcher)matcher).getExtension()); } if (matcher instanceof ExactFileNameMatcher) { final ExactFileNameMatcher exactFileNameMatcher = (ExactFileNameMatcher)matcher; Map<CharSequence, T> mapToUse = exactFileNameMatcher.isIgnoreCase() ? myExactFileNameAnyCaseMappings : myExactFileNameMappings; return mapToUse.get(exactFileNameMatcher.getFileName()); } for (Pair<FileNameMatcher, T> mapping : myMatchingMappings) { if (matcher.equals(mapping.getFirst())) return mapping.getSecond(); } return null; } @Deprecated @NotNull public String[] getAssociatedExtensions(@NotNull T type) { List<String> exts = new ArrayList<>(); for (Map.Entry<CharSequence, T> entry : myExtensionMappings.entrySet()) { if (entry.getValue() == type) { exts.add(entry.getKey().toString()); } } return ArrayUtil.toStringArray(exts); } @NotNull public FileTypeAssocTable<T> copy() { return new FileTypeAssocTable<>(myExtensionMappings, myExactFileNameMappings, myExactFileNameAnyCaseMappings, myMatchingMappings); } @NotNull public List<FileNameMatcher> getAssociations(@NotNull T type) { List<FileNameMatcher> result = new ArrayList<>(); for (Pair<FileNameMatcher, T> mapping : myMatchingMappings) { if (mapping.getSecond() == type) { result.add(mapping.getFirst()); } } for (Map.Entry<CharSequence, T> entries : myExactFileNameMappings.entrySet()) { if (entries.getValue() == type) { result.add(new ExactFileNameMatcher(entries.getKey().toString())); } } for (Map.Entry<CharSequence, T> entries : myExactFileNameAnyCaseMappings.entrySet()) { if (entries.getValue() == type) { result.add(new ExactFileNameMatcher(entries.getKey().toString(), true)); } } for (Map.Entry<CharSequence, T> entries : myExtensionMappings.entrySet()) { if (entries.getValue() == type) { result.add(new ExtensionFileNameMatcher(entries.getKey().toString())); } } return result; } boolean hasAssociationsFor(@NotNull T fileType) { if (myExtensionMappings.values().contains(fileType) || myExactFileNameMappings.values().contains(fileType) || myExactFileNameAnyCaseMappings.values().contains(fileType)) { return true; } for (Pair<FileNameMatcher, T> mapping : myMatchingMappings) { if (mapping.getSecond() == fileType) { return true; } } return false; } public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } FileTypeAssocTable<?> that = (FileTypeAssocTable)o; return myExtensionMappings.equals(that.myExtensionMappings) && myMatchingMappings.equals(that.myMatchingMappings) && myExactFileNameMappings.equals(that.myExactFileNameMappings) && myExactFileNameAnyCaseMappings.equals(that.myExactFileNameAnyCaseMappings); } public int hashCode() { int result = myExtensionMappings.hashCode(); result = 31 * result + myMatchingMappings.hashCode(); result = 31 * result + myExactFileNameMappings.hashCode(); result = 31 * result + myExactFileNameAnyCaseMappings.hashCode(); return result; } }