diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt index 7c7ace45548..af8ddb1b6a4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt @@ -67,6 +67,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import java.util.* +import kotlin.collections.LinkedHashMap +import kotlin.collections.LinkedHashSet data class ExtractSuperInfo( val originalClass: KtClassOrObject, @@ -155,7 +157,7 @@ class ExtractSuperRefactoring( project.runSynchronouslyWithProgress(RefactoringBundle.message("detecting.possible.conflicts"), true) { runReadAction { - val usages = ArrayList() + val usages = LinkedHashSet() for (element in elementsToMove) { ReferencesSearch.search(element).mapTo(usages) { MoveRenameUsageInfo(it, element) } if (element is KtCallableDeclaration) { @@ -164,7 +166,7 @@ class ExtractSuperRefactoring( } } } - conflictChecker.checkAllConflicts(usages, conflicts) + conflictChecker.checkAllConflicts(usages, LinkedHashSet(), conflicts) if (targetParent is PsiDirectory) { ExtractSuperClassUtil.checkSuperAccessible(targetParent, conflicts, originalClass.toLightClass()) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/KotlinChangePackageRefactoring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/KotlinChangePackageRefactoring.kt index 2ba7888373d..7eee99f5f03 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/KotlinChangePackageRefactoring.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/changePackage/KotlinChangePackageRefactoring.kt @@ -57,7 +57,7 @@ class KotlinChangePackageRefactoring(val file: KtFile) { override fun verify(file: PsiFile) = null }, delegate = MoveDeclarationsDelegate.TopLevel, - updateInternalReferences = false + scanEntireFile = false ), Mover.Idle // we don't need to move any declarations physically ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt index 704ea23e5b0..376cba7da66 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt @@ -99,7 +99,6 @@ class MoveDeclarationToSeparateFileIntention : delegate = MoveDeclarationsDelegate.TopLevel, searchInCommentsAndStrings = false, searchInNonCode = false, - updateInternalReferences = true, moveCallback = MoveCallback { val newFile = directory.findFile(targetFileName) as KtFile val newDeclaration = newFile.declarations.first() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt index 81575769fea..38e8e0a6996 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationsDelegate.kt @@ -34,10 +34,10 @@ import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject sealed class MoveDeclarationsDelegate { abstract fun getContainerChangeInfo(originalDeclaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): ContainerChangeInfo - abstract fun findUsages(descriptor: MoveDeclarationsDescriptor): List + abstract fun findInternalUsages(descriptor: MoveDeclarationsDescriptor): List abstract fun collectConflicts( descriptor: MoveDeclarationsDescriptor, - usages: MutableList, + internalUsages: MutableSet, conflicts: MultiMap ) abstract fun preprocessDeclaration(descriptor: MoveDeclarationsDescriptor, originalDeclaration: KtNamedDeclaration) @@ -49,11 +49,11 @@ sealed class MoveDeclarationsDelegate { ContainerInfo.Package(moveTarget.targetContainerFqName!!)) } - override fun findUsages(descriptor: MoveDeclarationsDescriptor): List = emptyList() + override fun findInternalUsages(descriptor: MoveDeclarationsDescriptor): List = emptyList() override fun collectConflicts( descriptor: MoveDeclarationsDescriptor, - usages: MutableList, + internalUsages: MutableSet, conflicts: MultiMap ) { @@ -83,17 +83,17 @@ sealed class MoveDeclarationsDelegate { return ContainerChangeInfo(originalInfo, newInfo) } - override fun findUsages(descriptor: MoveDeclarationsDescriptor): List { + override fun findInternalUsages(descriptor: MoveDeclarationsDescriptor): List { val classToMove = descriptor.elementsToMove.singleOrNull() as? KtClass ?: return emptyList() return collectOuterInstanceReferences(classToMove) } override fun collectConflicts( descriptor: MoveDeclarationsDescriptor, - usages: MutableList, + internalUsages: MutableSet, conflicts: MultiMap ) { - val usageIterator = usages.iterator() + val usageIterator = internalUsages.iterator() while (usageIterator.hasNext()) { val usage = usageIterator.next() val element = usage.element ?: continue diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt index e87838cf5a6..8adc19c51f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt @@ -37,21 +37,16 @@ import com.intellij.usageView.UsageViewBundle import com.intellij.usageView.UsageViewDescriptor import com.intellij.usageView.UsageViewUtil import com.intellij.util.IncorrectOperationException -import com.intellij.util.SmartList import com.intellij.util.containers.MultiMap import gnu.trove.THashMap import gnu.trove.TObjectHashingStrategy import org.jetbrains.kotlin.asJava.elements.KtLightElement import org.jetbrains.kotlin.asJava.toLightElements -import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.core.deleteSingle import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName -import org.jetbrains.kotlin.idea.refactoring.move.UnqualifiableMoveRenameUsageInfo -import org.jetbrains.kotlin.idea.refactoring.move.createMoveUsageInfoIfPossible -import org.jetbrains.kotlin.idea.refactoring.move.getInternalReferencesToUpdateOnPackageNameChange +import org.jetbrains.kotlin.idea.refactoring.move.* import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinClassHandler -import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages import org.jetbrains.kotlin.idea.references.KtSimpleNameReference.ShorteningMode import org.jetbrains.kotlin.idea.search.projectScope import org.jetbrains.kotlin.psi.* @@ -92,7 +87,7 @@ class MoveDeclarationsDescriptor( val delegate: MoveDeclarationsDelegate, val searchInCommentsAndStrings: Boolean = true, val searchInNonCode: Boolean = true, - val updateInternalReferences: Boolean = true, + val scanEntireFile: Boolean = false, val deleteSourceFiles: Boolean = false, val moveCallback: MoveCallback? = null, val openInEditor: Boolean = false @@ -125,16 +120,19 @@ class MoveKotlinDeclarationsProcessor( return MoveMultipleElementsViewDescriptor(elementsToMove.toTypedArray(), targetContainerFqName) } - private val usagesToProcessBeforeMove = SmartList() - fun getConflictsAsUsages(): List = conflicts.entrySet().map { ConflictUsageInfo(it.key, it.value) } + class UsagesToProcessBeforeMoveWrapper( + sourceFile: KtFile, + val usages: Collection + ): UsageInfo(sourceFile) + public override fun findUsages(): Array { if (elementsToMove.isEmpty()) return UsageInfo.EMPTY_ARRAY val newContainerName = descriptor.moveTarget.targetContainerFqName?.asString() ?: "" - fun collectUsages(kotlinToLightElements: Map>, result: MutableList) { + fun collectUsages(kotlinToLightElements: Map>, result: MutableCollection) { kotlinToLightElements.values.flatMap { it }.flatMapTo(result) { lightElement -> val newFqName = StringUtil.getQualifiedName(newContainerName, lightElement.name) @@ -169,24 +167,38 @@ class MoveKotlinDeclarationsProcessor( } } + val usagesToProcessBeforeMove = LinkedHashSet() val usages = ArrayList() val conflictChecker = MoveConflictChecker(project, elementsToMove, descriptor.moveTarget, elementsToMove.first()) - for (kotlinToLightElements in kotlinToLightElementsBySourceFile.values) { - kotlinToLightElements.keys.forEach { - if (descriptor.updateInternalReferences) { + for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) { + val internalUsages = LinkedHashSet() + val externalUsages = LinkedHashSet() + + if (descriptor.scanEntireFile) { + val changeInfo = ContainerChangeInfo( + ContainerInfo.Package(sourceFile.packageFqName), + descriptor.moveTarget.targetContainerFqName?.let { ContainerInfo.Package(it) } ?: ContainerInfo.UnknownPackage + ) + internalUsages += sourceFile.getInternalReferencesToUpdateOnPackageNameChange(changeInfo) + } + else { + kotlinToLightElements.keys.forEach { val packageNameInfo = descriptor.delegate.getContainerChangeInfo(it, descriptor.moveTarget) - val (usagesToProcessLater, usagesToProcessEarly) = it - .getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo) - .partition { it is UnqualifiableMoveRenameUsageInfo } - usages.addAll(usagesToProcessLater) - usagesToProcessBeforeMove.addAll(usagesToProcessEarly) + internalUsages += it.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo) } } - usages += descriptor.delegate.findUsages(descriptor) - collectUsages(kotlinToLightElements, usages) - conflictChecker.checkAllConflicts(usages, conflicts) - descriptor.delegate.collectConflicts(descriptor, usages, conflicts) + internalUsages += descriptor.delegate.findInternalUsages(descriptor) + collectUsages(kotlinToLightElements, externalUsages) + conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts) + descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts) + + usages += externalUsages + + val (usagesToProcessLater, usagesToProcessEarly) = internalUsages.partition { it is UnqualifiableMoveRenameUsageInfo } + usages += usagesToProcessLater + usagesToProcessBeforeMove += usagesToProcessEarly + usages += UsagesToProcessBeforeMoveWrapper(sourceFile, usagesToProcessBeforeMove) } descriptor.delegate.collectConflicts(descriptor, usagesToProcessBeforeMove, conflicts) @@ -217,7 +229,12 @@ class MoveKotlinDeclarationsProcessor( try { val usageList = usages.toList() - descriptor.delegate.preprocessUsages(project, usageList) + val (usagesToProcessBeforeMoveWrappers, usagesToProcessAfterMove) = + usageList.partition { it is UsagesToProcessBeforeMoveWrapper } + val usagesToProcessBeforeMove = usagesToProcessBeforeMoveWrappers.flatMap { (it as UsagesToProcessBeforeMoveWrapper).usages } + + descriptor.delegate.preprocessUsages(project, usagesToProcessBeforeMove) + descriptor.delegate.preprocessUsages(project, usagesToProcessAfterMove) postProcessMoveUsages(usagesToProcessBeforeMove, shorteningMode = ShorteningMode.NO_SHORTENING) @@ -268,7 +285,7 @@ class MoveKotlinDeclarationsProcessor( } } - nonCodeUsages = postProcessMoveUsages(usageList, oldToNewElementsMapping).toTypedArray() + nonCodeUsages = postProcessMoveUsages(usagesToProcessAfterMove, oldToNewElementsMapping).toTypedArray() } catch (e: IncorrectOperationException) { nonCodeUsages = null @@ -286,8 +303,4 @@ class MoveKotlinDeclarationsProcessor( } override fun getCommandName(): String = REFACTORING_NAME -} - -interface D : DeclarationDescriptorWithSource { - } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt index 3f47cb1a0f9..92fe5bda77e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt @@ -140,15 +140,28 @@ class MoveConflictChecker( private fun render(declaration: PsiElement) = RefactoringUIUtil.getDescription(declaration, false) - fun checkModuleConflictsInUsages(usages: List, conflicts: MultiMap) { + fun checkModuleConflictsInUsages(externalUsages: MutableSet, conflicts: MultiMap) { + val newConflicts = MultiMap() val sourceRoot = moveTarget.targetFile ?: return - RefactoringConflictsUtil.analyzeModuleConflicts(project, elementsToMove, usages.toTypedArray(), sourceRoot, conflicts) + RefactoringConflictsUtil.analyzeModuleConflicts(project, elementsToMove, externalUsages.toTypedArray(), sourceRoot, newConflicts) + if (!newConflicts.isEmpty) { + val referencedElementsToSkip = newConflicts.keySet().mapNotNullTo(HashSet()) { it.namedUnwrappedElement } + externalUsages.removeIf { + it is MoveRenameUsageInfo && + it.referencedElement?.namedUnwrappedElement?.let { it in referencedElementsToSkip } ?: false + } + conflicts.putAllValues(newConflicts) + } } - fun checkModuleConflictsInDeclarations(conflicts: MultiMap) { + private fun checkModuleConflictsInDeclarations( + internalUsages: MutableSet, + conflicts: MultiMap + ) { val sourceRoot = moveTarget.targetFile ?: return val targetModule = ModuleUtilCore.findModuleForFile(sourceRoot, project) ?: return val resolveScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(targetModule) + val referencesToSkip = HashSet() for (declaration in elementsToMove - doNotGoIn) { declaration.forEachDescendantOfType { refExpr -> val targetDescriptor = refExpr.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, refExpr] ?: return@forEachDescendantOfType @@ -172,11 +185,13 @@ class MoveConflictChecker( scopeDescription, CommonRefactoringUtil.htmlEmphasize(targetModule.name)) conflicts.putValue(target, CommonRefactoringUtil.capitalize(message)) + referencesToSkip += refExpr } } + internalUsages.removeIf { it.reference?.element?.let { it in referencesToSkip } ?: false } } - fun checkVisibilityInUsages(usages: List, conflicts: MultiMap) { + fun checkVisibilityInUsages(usages: Collection, conflicts: MultiMap) { val declarationToContainers = HashMap>() for (usage in usages) { val element = usage.element @@ -205,7 +220,7 @@ class MoveConflictChecker( } } - fun checkVisibilityInDeclarations(conflicts: MultiMap) { + private fun checkVisibilityInDeclarations(conflicts: MultiMap) { val targetContainer = moveTarget.getContainerDescriptor() ?: return for (declaration in elementsToMove - doNotGoIn) { declaration.forEachDescendantOfType { refExpr -> @@ -227,10 +242,14 @@ class MoveConflictChecker( } } - fun checkAllConflicts(usages: List, conflicts: MultiMap) { - checkModuleConflictsInUsages(usages, conflicts) - checkModuleConflictsInDeclarations(conflicts) - checkVisibilityInUsages(usages, conflicts) + fun checkAllConflicts( + externalUsages: MutableSet, + internalUsages: MutableSet, + conflicts: MultiMap + ) { + checkModuleConflictsInUsages(externalUsages, conflicts) + checkModuleConflictsInDeclarations(internalUsages, conflicts) + checkVisibilityInUsages(externalUsages, conflicts) checkVisibilityInDeclarations(conflicts) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt index dd43aba0ac7..dcb00c5390d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveFilesOrDirectories/MoveKotlinFileHandler.kt @@ -91,7 +91,7 @@ class MoveKotlinFileHandler : MoveFileHandler() { elementsToMove = psiFile.declarations.filterIsInstance(), moveTarget = moveTarget, delegate = MoveDeclarationsDelegate.TopLevel, - updateInternalReferences = false + scanEntireFile = true ), Mover.Idle ) @@ -122,7 +122,6 @@ class MoveKotlinFileHandler : MoveFileHandler() { usages += it.findUsages() usages += it.getConflictsAsUsages() } - newParent?.let { usages += findInternalUsages(psiFile, it) } return usages } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index 3e7c8babae7..e0730e9bab2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -303,7 +303,7 @@ private fun updateJavaReference(reference: PsiReferenceExpression, oldElement: P /** * Perform usage postprocessing and return non-code usages */ -fun postProcessMoveUsages(usages: List, +fun postProcessMoveUsages(usages: Collection, oldToNewElementsMapping: Map = Collections.emptyMap(), shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING ): List { diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/after/A/src/a2/internalSource.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/after/A/src/a2/internalSource.kt index 8e6bf6b314a..6ca006a1306 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/after/A/src/a2/internalSource.kt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/after/A/src/a2/internalSource.kt @@ -1,5 +1,5 @@ package a2 -import b.internalTargetVal +import a1.internalTargetVal -internal val sourceVal = b.internalTargetVal \ No newline at end of file +internal val sourceVal = internalTargetVal \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/conflicts.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/conflicts.txt index 8449d2eaf9e..bb5400d81f7 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/conflicts.txt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveFileWithDeclarationsToUnrelatedModuleConflict/conflicts.txt @@ -1,3 +1,2 @@ Property a1.internalTargetVal, referenced in file internalSource.kt, will not be accessible from module A -Property a1.internalTargetVal, referenced in file internalSource.kt, will not be accessible from module A -Property sourceVal uses property internalTargetVal which will be inaccessible after move \ No newline at end of file +Property a1.internalTargetVal, referenced in file internalSource.kt, will not be accessible from module A \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/after/A/src/a/test.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/after/A/src/a/test.kt index 807e1598f91..e442b781b72 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/after/A/src/a/test.kt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/after/A/src/a/test.kt @@ -1,3 +1,3 @@ package a -val val2 = target.val1 \ No newline at end of file +val val2 = val1 \ No newline at end of file diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/conflicts.txt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/conflicts.txt index 7a621710d08..9ffc4da0557 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/conflicts.txt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/moveToUnrelatedModuleConflict/conflicts.txt @@ -1,2 +1 @@ Property a.val1, referenced in file test.kt, will not be accessible from module A -Property a.val1, referenced in file test.kt, will not be accessible from module A diff --git a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/visibilityConflictInImport/after/A/src/a2/internalSource.kt b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/visibilityConflictInImport/after/A/src/a2/internalSource.kt index 7a24f3edbf4..a76eda225e4 100644 --- a/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/visibilityConflictInImport/after/A/src/a2/internalSource.kt +++ b/idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/visibilityConflictInImport/after/A/src/a2/internalSource.kt @@ -1,3 +1,3 @@ package a2 -import b.internalTargetVal \ No newline at end of file +import a1.internalTargetVal \ No newline at end of file