private final ResourceInspectionResult inspectResource()

in source/com.microsoft.tfs.client.eclipse/src/com/microsoft/tfs/client/eclipse/TFSMoveDeleteHook.java [1225:1560]


    private final ResourceInspectionResult inspectResource(
        final IResourceTree tree,
        final IResource resource,
        final boolean resourceMustExist,
        final boolean deferIgnoredResources,
        final ChangeType deniedChangeTypes,
        final int updateFlags,
        final boolean deletingFolder,
        final IProgressMonitor progressMonitor) {
        progressMonitor.beginTask(MessageFormat.format(
            Messages.getString("TFSMoveDeleteHook.ExaminingResourceFormat"), //$NON-NLS-1$
            resource.getFullPath()), 10);

        try {
            /* File is a linked resource, exit quickly */
            if (resource.getLocation() == null || resource.isLinked(IResource.CHECK_ANCESTORS)) {
                log.info(MessageFormat.format("Cannot move/delete linked resource {0}", resource)); //$NON-NLS-1$
                return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
            }
            /* Resource has already been removed from the workspace */
            else if (resourceMustExist && !resource.exists()) {
                log.warn(
                    MessageFormat.format("Resource {0} no longer exists in the workspace.", resource.getFullPath())); //$NON-NLS-1$
            }

            final boolean isIgnored = IGNORED_RESOURCES_FILTER.filter(resource).isReject();
            if (deferIgnoredResources && isIgnored) {
                log.debug(MessageFormat.format("Ignoring resource {0}", resource)); //$NON-NLS-1$
                return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
            }

            final String resourcePath = resource.getLocation().toOSString();

            if (resourceMustExist) {
                final File resourceFile = new File(resourcePath);

                /* Ensure that the file exists on disk */
                if (!resourceFile.exists()) {
                    log.warn(MessageFormat.format("Resource {0} no longer exists on disk", resourcePath)); //$NON-NLS-1$
                }

                /*
                 * Ensure that the type in the tree is the same type as on the
                 * file system
                 */
                if (resource instanceof IFile && resourceFile.isDirectory()) {
                    throw new RuntimeException(MessageFormat.format(
                        //@formatter:off
                        Messages.getString("TFSMoveDeleteHook.ResourceExistsAsFileInWorkspaceButFolderOnDiskFormat"), //$NON-NLS-1$
                        //@formatter:on
                        resource.getFullPath()));
                } else if (!(resource instanceof IFile) && resourceFile.isFile()) {
                    throw new RuntimeException(MessageFormat.format(
                        //@formatter:off
                        Messages.getString("TFSMoveDeleteHook.ResourceExistsAsFolderInWorkspaceButFileOnDiskFormat"), //$NON-NLS-1$
                        //@formatter:on
                        resource.getFullPath()));
                }
            }

            /* See if the tree is in sync */
            final int depth = (resource instanceof IFile) ? IResource.DEPTH_ZERO : IResource.DEPTH_INFINITE;
            if ((updateFlags & IResource.FORCE) != IResource.FORCE && !tree.isSynchronized(resource, depth)) {
                throw new RuntimeException(
                    MessageFormat.format(
                        Messages.getString("TFSMoveDeleteHook.ResourceNotInSyncWithLocalFileSystemFormat"), //$NON-NLS-1$
                        resource.getFullPath()));
            }

            /* See if we're online */
            final ResourceRepositoryMap repositoryMap = PluginResourceHelpers.mapResources(new IResource[] {
                resource
            });
            final TFSRepository repository = repositoryMap.getRepository(resource);

            /* Offline */
            if (repository == null) {
                log.info(
                    MessageFormat.format(
                        "Repository is offline, delete for {0} must be reconciled when returning online to Team Foundation Server.", //$NON-NLS-1$
                        resource.getFullPath()));

                return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
            } else if (!repository.equals(repositoryProvider.getRepository())) {
                log.warn(
                    MessageFormat.format(
                        "Repository for resource {0} mapped to {1} but repository provider returned {2}", //$NON-NLS-1$
                        resource.getFullPath(),
                        repository,
                        repositoryProvider.getRepository()));
            }

            /*
             * Test if this item has an invalid TFS name/path (which would cause
             * errors in the queries below).
             */
            try {
                repository.getWorkspace().getMappedServerPath(resourcePath);
            } catch (final InputValidationException e) {
                log.warn(MessageFormat.format("Ignoring resource {0} because it is not a valid TFS path", resource), e); //$NON-NLS-1$
                return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
            }

            /*
             * See if the resource exists in the ResourceDataManager (ie, if it
             * exists in TFS). If the project exists in TFS but the file is not,
             * then ResourceDataManager.hasCompletedRefresh will be true but
             * ResourceDataManager.getResourceData will be null.
             */
            final boolean needsQuery;

            if (resourceMustExist) {
                final ResourceDataManager resourceDataManager =
                    TFSEclipseClientPlugin.getDefault().getResourceDataManager();

                if (!resourceDataManager.hasCompletedRefresh(resource.getProject())
                    || resourceDataManager.getResourceData(resource) == null) {
                    needsQuery = true;
                } else {
                    needsQuery = false;
                }
            } else {
                needsQuery = false;
            }

            /*
             * This is a particularly complicated and ugly work-around for
             * server workspaces and Eclipse's copy and paste behavior.
             *
             * JDT (and probably others) implements "paste" that overwrites an
             * existing file by:
             *
             * 1. Pending an edit on the target
             *
             * 2. Deleting the target
             *
             * 3. Copying the source to the target
             *
             * In a server workspace, the user may have the
             * "check out files in background" option enabled, which uses
             * another thread to do the checkout. The problem is that we may
             * arrive at this point in this method before the background thread
             * (started at step #1) has completed the checkout. Querying the
             * pending changes might miss the "edit" change that we need to know
             * about to do the MoveDeleteHook work.
             *
             * So we we must wait if the file is being checked out in the
             * background.
             */
            if (repository.getWorkspace().getLocation() == WorkspaceLocation.SERVER && resource instanceof IFile) {
                final TFSFileModificationValidator validator = repositoryProvider.getTFSFileModificationValidator();

                try {
                    new ExtensionPointAsyncObjectWaiter().waitUntilTrue(new IAsyncObjectWaiter.Predicate() {
                        @Override
                        public boolean isTrue() {
                            return !validator.isCheckingOutFileInBackground((IFile) resource);
                        }
                    });
                } catch (final InterruptedException e) {
                    throw new VersionControlException(
                        MessageFormat.format(
                            Messages.getString("TFSMoveDeleteHook.InterruptedWaitingForBackgroundCheckoutFormat"), //$NON-NLS-1$
                            resource.getName()));
                }
            }

            final List<PendingChange> pendingChanges = getNestedPendingChanges(repository, resourcePath);
            log.info("Nested pending changes count: [" + String.valueOf(pendingChanges.size()) + "]"); //$NON-NLS-1$ //$NON-NLS-2$

            if (deletingFolder) {
                /*
                 * Test if there is a pending rename with the source path
                 * including the item's path. That happens when Eclipse cleans
                 * up empty folders left after a package renaming. We don't need
                 * to do anything and skip the operation in the Eclipse
                 * platform.
                 */
                final String serverPath = repository.getWorkspace().getMappedServerPath(resourcePath);
                log.info("Local folder to be deleted: " + resourcePath); //$NON-NLS-1$
                log.info("Server folder to be deleted: " + serverPath); //$NON-NLS-1$

                boolean reject = false;
                for (final PendingChange change : pendingChanges) {
                    if (change.getChangeType().contains(ChangeType.RENAME)
                        && !isSamePendingChange(serverPath, resourcePath, change)) {
                        log.info("Child pending change:"); //$NON-NLS-1$
                        log.info("  ChangeType: " + change.getChangeType().toString()); //$NON-NLS-1$
                        log.info("    ServerItem: " + change.getServerItem()); //$NON-NLS-1$
                        log.info("    LocalItem: " + (change.getLocalItem() == null ? "null" : change.getLocalItem())); //$NON-NLS-1$ //$NON-NLS-2$
                        log.info("    SourceLocalItem: " //$NON-NLS-1$
                            + (change.getSourceLocalItem() == null ? "null" : change.getSourceLocalItem())); //$NON-NLS-1$
                        log.info("    SourceServerItem: " //$NON-NLS-1$
                            + (change.getSourceServerItem() == null ? "null" : change.getSourceServerItem())); //$NON-NLS-1$
                        log.info("    Date: " //$NON-NLS-1$
                            + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS z").format( //$NON-NLS-1$
                                change.getWebServiceObject().getDate().getTime()));

                        reject = true;
                    }
                }

                if (reject) {
                    progressMonitor.worked(1);

                    return new ResourceInspectionResult(
                        ResourceInspectionStatus.WARN,
                        repository,
                        true,
                        pendingChanges.toArray(new PendingChange[pendingChanges.size()]),
                        isIgnored,
                        Messages.getString("TFSMoveDeleteHook.CannotDeleteFolder")); //$NON-NLS-1$
                }
            }

            /*
             * If there are pending changes, see if they are of a disallowed
             * type.
             */
            boolean reject = false;
            for (final PendingChange change : pendingChanges) {
                if (change.getChangeType().containsAny(deniedChangeTypes)) {
                    log.info("  ChangeType: " + change.getChangeType().toString()); //$NON-NLS-1$
                    log.info("    ServerItem: " + change.getServerItem()); //$NON-NLS-1$
                    log.info("    LocalItem: " + (change.getLocalItem() == null ? "null" : change.getLocalItem())); //$NON-NLS-1$ //$NON-NLS-2$
                    log.info("    SourceLocalItem: " //$NON-NLS-1$
                        + (change.getSourceLocalItem() == null ? "null" : change.getSourceLocalItem())); //$NON-NLS-1$
                    log.info("    SourceServerItem: " //$NON-NLS-1$
                        + (change.getSourceServerItem() == null ? "null" : change.getSourceServerItem())); //$NON-NLS-1$
                    log.info("    Date: " //$NON-NLS-1$
                        + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS z").format( //$NON-NLS-1$
                            change.getWebServiceObject().getDate().getTime()));

                    reject = true;
                }
            }

            if (reject) {
                log.warn(MessageFormat.format(
                    //@formatter:off
                    Messages.getString("TFSMoveDeleteHook.ResourceHasPendingChangePleaseUndoBeforeContinuingFormat"), //$NON-NLS-1$
                    //@formatter:on
                    resource.getLocation().toOSString()));

                progressMonitor.worked(1);

                throw new RuntimeException(MessageFormat.format(
                    //@formatter:off
                    Messages.getString("TFSMoveDeleteHook.ResourceHasPendingChangePleaseUndoBeforeContinuingFormat"), //$NON-NLS-1$
                    //@formatter:on
                    resource.getLocation().toOSString()));
            }

            /*
             * If there are no pending changes and we didn't have any
             * ResourceData for this resource, we need to query the server to
             * make sure the item exists.
             */
            boolean inServer = true;

            if (needsQuery && WorkspaceLocation.LOCAL.equals(repository.getWorkspace().getLocation())) {
                /*
                 * Local workspaces should query extended items to avoid hitting
                 * the server.
                 */
                final QueryItemsExtendedCommand queryCommand =
                    new QueryItemsExtendedCommand(repository, new ItemSpec[] {
                        new ItemSpec(resourcePath, RecursionType.NONE)
                }, DeletedState.NON_DELETED, ItemType.ANY, GetItemsOptions.LOCAL_ONLY);

                final IStatus queryStatus = runCommand(queryCommand, new SubProgressMonitor(progressMonitor, 9));

                if (!queryStatus.isOK()) {
                    throw new RuntimeException(queryStatus.getMessage());
                }

                final ExtendedItem[][] items = queryCommand.getItems();

                if (items == null || items.length == 0 || items[0] == null || items[0].length == 0) {
                    /*
                     * The item does not exist on the server (and is not an
                     * implicit add.)
                     */
                    return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
                }
            } else if (needsQuery) {
                final QueryItemsCommand queryCommand = new QueryItemsCommand(
                    repository,
                    new ItemSpec[] {
                        new ItemSpec(resourcePath, RecursionType.NONE)
                },
                    new WorkspaceVersionSpec(repository.getWorkspace()),
                    DeletedState.NON_DELETED,
                    ItemType.ANY,
                    GetItemsOptions.NONE);

                final IStatus queryStatus = runCommand(queryCommand, new SubProgressMonitor(progressMonitor, 9));

                if (!queryStatus.isOK()) {
                    throw new RuntimeException(queryStatus.getMessage());
                }

                final ItemSet[] itemSets = queryCommand.getItemSets();

                /* Item does not exist in TFS */
                if (itemSets == null
                    || itemSets.length == 0
                    || itemSets[0].getItems() == null
                    || itemSets[0].getItems().length == 0) {
                    /*
                     * Special case: pending adds of files may implicitly add a
                     * folder. If this file does not exist on the server and is
                     * not an implicit add, ignore it.
                     */
                    if (pendingChanges.size() == 0) {
                        return new ResourceInspectionResult(ResourceInspectionStatus.DEFER);
                    }

                    /*
                     * An implicit add that does not exist on the server.
                     */
                    inServer = false;
                }
            }

            /* Allow the deleteFile/deleteFolder methods to continue. */
            progressMonitor.worked(1);
            return new ResourceInspectionResult(
                repository,
                inServer,
                pendingChanges.toArray(new PendingChange[pendingChanges.size()]),
                isIgnored);
        } finally {
            progressMonitor.done();
        }
    }