/* * 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.stubs; import com.intellij.lang.Language; import com.intellij.lang.LanguageParserDefinitions; import com.intellij.lang.ParserDefinition; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.IStubFileElementType; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.IndexInfrastructure; import com.intellij.util.indexing.IndexingStamp; import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TLongObjectHashMap; import gnu.trove.TObjectLongHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; class StubVersionMap { private static final String INDEXED_FILETYPES = "indexed_filetypes"; private static final String RECORD_SEPARATOR = "\uFFFF"; private static final String LINE_SEPARATOR = "\n"; private static final Charset ourEncoding = CharsetToolkit.UTF8_CHARSET; private final Map<FileType, Object> fileTypeToVersionOwner = new THashMap<>(); private final TObjectLongHashMap<FileType> fileTypeToVersion = new TObjectLongHashMap<>(); private final TLongObjectHashMap<FileType> versionToFileType = new TLongObjectHashMap<>(); private long myStubIndexStamp; StubVersionMap() throws IOException { for (final FileType fileType : FileTypeRegistry.getInstance().getRegisteredFileTypes()) { Object owner = getVersionOwner(fileType); if (owner != null) { fileTypeToVersionOwner.put(fileType, owner); } } updateState(); } private void updateState() throws IOException { final long currentStubIndexStamp = IndexingStamp.getIndexCreationStamp(StubUpdatingIndex.INDEX_ID); File allIndexedFiles = allIndexedFilesRegistryFile(); List<String> removedFileTypes = new ArrayList<>(); List<FileType> updatedFileTypes = new ArrayList<>(); List<FileType> addedFileTypes = new ArrayList<>(); long lastUsedCounter = currentStubIndexStamp; boolean canUsePreviousMappings = allIndexedFiles.exists(); FileTypeRegistry fileTypeRegistry = FileTypeRegistry.getInstance(); Set<FileType> loadedFileTypes = new THashSet<>(); if (canUsePreviousMappings) { List<String> stringList = StringUtil.split(FileUtil.loadFile(allIndexedFiles, ourEncoding), LINE_SEPARATOR); long allIndexedFilesVersion = Long.parseLong(stringList.get(0)); if (allIndexedFilesVersion == currentStubIndexStamp) { for (int i = 1, size = stringList.size(); i < size; ++i) { List<String> strings = StringUtil.split(stringList.get(i), RECORD_SEPARATOR); String fileTypeName = strings.get(0); long usedTimeStamp = Long.parseLong(strings.get(2)); lastUsedCounter = Math.min(lastUsedCounter, usedTimeStamp); FileType fileType = fileTypeRegistry.findFileTypeByName(fileTypeName); if (fileType == null) removedFileTypes.add(fileTypeName); else { loadedFileTypes.add(fileType); Object owner = getVersionOwner(fileType); if (owner == null) removedFileTypes.add(fileTypeName); else { if (!Comparing.equal(strings.get(1), typeAndVersion(owner))) { updatedFileTypes.add(fileType); } else { registerStamp(fileType, usedTimeStamp); } } } } } else { canUsePreviousMappings = false; } } for(FileType fileType:fileTypeToVersionOwner.keySet()) { if (!loadedFileTypes.contains(fileType)) { addedFileTypes.add(fileType); } } if (canUsePreviousMappings && (!addedFileTypes.isEmpty() || !removedFileTypes.isEmpty())) { StubUpdatingIndex.LOG.info("requesting complete stub index rebuild due to changes: " + (addedFileTypes.isEmpty() ? "" : "added file types:" + StringUtil.join(addedFileTypes, FileType::getName, ",") + ";") + (removedFileTypes.isEmpty() ? "":"removed file types:" + StringUtil.join(removedFileTypes, ","))); throw new IOException(); // StubVersionMap will be recreated } long counter = lastUsedCounter - 1; // important to start with value smaller and progress downwards for(FileType fileType: ContainerUtil.concat(updatedFileTypes, addedFileTypes)) { while (versionToFileType.containsKey(counter)) --counter; registerStamp(fileType, counter); } if (!addedFileTypes.isEmpty() || !updatedFileTypes.isEmpty() || !removedFileTypes.isEmpty()) { if (!addedFileTypes.isEmpty()) { StubUpdatingIndex.LOG.info("Following new file types will be indexed:" + StringUtil.join(addedFileTypes, FileType::getName, ",")); } if (!updatedFileTypes.isEmpty()) { StubUpdatingIndex.LOG.info("Stub version was changed for " + StringUtil.join(updatedFileTypes, FileType::getName, ",")); } if (!removedFileTypes.isEmpty()) { StubUpdatingIndex.LOG.info("Following file types will not be indexed:" + StringUtil.join(removedFileTypes, ",")); } StringBuilder allFileTypes = new StringBuilder(); allFileTypes.append(currentStubIndexStamp).append(LINE_SEPARATOR); for (FileType fileType : fileTypeToVersionOwner.keySet()) { Object owner = fileTypeToVersionOwner.get(fileType); long timestamp = fileTypeToVersion.get(fileType); allFileTypes.append(fileType.getName()).append(RECORD_SEPARATOR).append(typeAndVersion(owner)).append(RECORD_SEPARATOR) .append(timestamp).append(LINE_SEPARATOR); } FileUtil.writeToFile(allIndexedFiles, allFileTypes.toString().getBytes(ourEncoding)); } myStubIndexStamp = currentStubIndexStamp; } private void registerStamp(FileType fileTypeByName, long stamp) { fileTypeToVersion.put(fileTypeByName, stamp); FileType previousType = versionToFileType.put(stamp, fileTypeByName); if (previousType != null) { assert false; } } private static Object getVersionOwner(FileType fileType) { Object owner = null; if (fileType instanceof LanguageFileType) { Language l = ((LanguageFileType)fileType).getLanguage(); ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(l); if (parserDefinition != null) { final IFileElementType type = parserDefinition.getFileNodeType(); if (type instanceof IStubFileElementType) { owner = type; } } } BinaryFileStubBuilder builder = BinaryFileStubBuilders.INSTANCE.forFileType(fileType); if (builder != null) { owner = builder; } return owner; } public long getStamp(FileType type) { return fileTypeToVersion.get(type); } void clear() { fileTypeToVersion.clear(); versionToFileType.clear(); try { updateState(); } catch (IOException ex) { throw new RuntimeException(ex); } } @NotNull private static File allIndexedFilesRegistryFile() { return new File(new File(IndexInfrastructure.getIndexRootDir(StubUpdatingIndex.INDEX_ID), ".fileTypes"), INDEXED_FILETYPES); } @NotNull private static String typeAndVersion(Object owner) { return info(owner) + "," + version(owner); } private static String info(Object owner) { if (owner instanceof IStubFileElementType) { return "stub:" + owner.getClass().getName(); } else { return "binary stub builder:" + owner.getClass().getName(); } } private static int version(Object owner) { if (owner instanceof IStubFileElementType) { return ((IStubFileElementType)owner).getStubVersion(); } else { return ((BinaryFileStubBuilder)owner).getStubVersion(); } } public int getIndexingTimestampDiffForFileType(FileType type) { return (int)(myStubIndexStamp - fileTypeToVersion.get(type)); } public @Nullable FileType getFileTypeByIndexingTimestampDiff(int diff) { return versionToFileType.get(myStubIndexStamp - diff); } }