/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.resources.impl; import com.google.common.annotations.Beta; import com.google.common.base.Optional; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.core.model.project.NewProjectConfig; import org.eclipse.che.api.core.model.project.ProjectConfig; import org.eclipse.che.api.core.model.project.SourceStorage; import org.eclipse.che.api.core.rest.shared.dto.Link; import org.eclipse.che.api.project.shared.dto.ItemReference; import org.eclipse.che.api.project.shared.dto.SourceEstimation; import org.eclipse.che.api.project.shared.dto.TreeElement; import org.eclipse.che.api.promises.client.Function; import org.eclipse.che.api.promises.client.FunctionException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseProvider; import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.ProjectProblemDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.event.ng.ClientServerEventService; import org.eclipse.che.ide.api.event.ng.DeletedFilesController; import org.eclipse.che.ide.api.machine.DevMachine; import org.eclipse.che.ide.api.machine.WsAgentURLModifier; import org.eclipse.che.ide.api.project.MutableProjectConfig; import org.eclipse.che.ide.api.project.ProjectServiceClient; import org.eclipse.che.ide.api.project.QueryExpression; import org.eclipse.che.ide.api.project.type.ProjectTypeRegistry; import org.eclipse.che.ide.api.resources.Container; import org.eclipse.che.ide.api.resources.File; import org.eclipse.che.ide.api.resources.Folder; import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Project.ProblemProjectMarker; import org.eclipse.che.ide.api.resources.Project.ProjectRequest; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.resources.ResourceChangedEvent; import org.eclipse.che.ide.api.resources.ResourceDelta; import org.eclipse.che.ide.api.resources.marker.Marker; import org.eclipse.che.ide.api.resources.marker.MarkerChangedEvent; import org.eclipse.che.ide.context.AppContextImpl; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.resource.Path; import org.eclipse.che.ide.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Optional.of; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.System.arraycopy; import static java.util.Arrays.copyOf; import static org.eclipse.che.ide.api.resources.Resource.FILE; import static org.eclipse.che.ide.api.resources.ResourceDelta.ADDED; import static org.eclipse.che.ide.api.resources.ResourceDelta.COPIED_FROM; import static org.eclipse.che.ide.api.resources.ResourceDelta.DERIVED; import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_FROM; import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_TO; import static org.eclipse.che.ide.api.resources.ResourceDelta.REMOVED; import static org.eclipse.che.ide.api.resources.ResourceDelta.SYNCHRONIZED; import static org.eclipse.che.ide.api.resources.ResourceDelta.UPDATED; import static org.eclipse.che.ide.util.Arrays.add; import static org.eclipse.che.ide.util.Arrays.removeAll; import static org.eclipse.che.ide.util.NameUtils.checkFileName; import static org.eclipse.che.ide.util.NameUtils.checkFolderName; import static org.eclipse.che.ide.util.NameUtils.checkProjectName; /** * Acts as the service lay between the user interactions with resources and data transfer layer. * Main necessity of this manager is to encapsulate business logic which serves resources from * the interfaces. * <p/> * This manager is not intended to be operated with third-party components. Only resources can * operate with it. To operate with resources use {@link AppContext#getWorkspaceRoot()} and * {@link AppContext#getProjects()}. * * @author Vlad Zhukovskiy * @see AppContext * @see AppContextImpl * @since 4.4.0 */ @Beta public final class ResourceManager { /** * Describes zero depth level for the descendants. */ private static final int DEPTH_ZERO = 0; /** * Describes first depth level for the descendants. */ private static final int DEPTH_ONE = 1; /** * Relative link for the content url. * * @see #newResourceFrom(ItemReference) */ private static final String GET_CONTENT_REL = "get content"; /** * Empty projects container. * * @see #getWorkspaceProjects() */ private static final Project[] NO_PROJECTS = new Project[0]; private static final Resource[] NO_RESOURCES = new Resource[0]; private final ProjectServiceClient ps; private final EventBus eventBus; private final EditorAgent editorAgent; private final DeletedFilesController deletedFilesController; private final ResourceFactory resourceFactory; private final PromiseProvider promises; private final DtoFactory dtoFactory; private final ProjectTypeRegistry typeRegistry; /** * Link to the workspace content root. Immutable among the workspace life. */ private final Container workspaceRoot; private final WsAgentURLModifier urlModifier; private final ClientServerEventService clientServerEventService; private DevMachine devMachine; /** * Internal store, which caches requested resources from the server. */ private ResourceStore store; /** * Cached dto project configuration. */ private ProjectConfigDto[] cachedConfigs; @Inject public ResourceManager(@Assisted DevMachine devMachine, ProjectServiceClient ps, EventBus eventBus, EditorAgent editorAgent, DeletedFilesController deletedFilesController, ResourceFactory resourceFactory, PromiseProvider promises, DtoFactory dtoFactory, ProjectTypeRegistry typeRegistry, ResourceStore store, WsAgentURLModifier urlModifier, ClientServerEventService clientServerEventService) { this.devMachine = devMachine; this.ps = ps; this.eventBus = eventBus; this.editorAgent = editorAgent; this.deletedFilesController = deletedFilesController; this.resourceFactory = resourceFactory; this.promises = promises; this.dtoFactory = dtoFactory; this.typeRegistry = typeRegistry; this.store = store; this.urlModifier = urlModifier; this.clientServerEventService = clientServerEventService; this.workspaceRoot = resourceFactory.newFolderImpl(Path.ROOT, this); } /** * Returns the workspace registered projects. * * @return the {@link Promise} with registered projects * @see Project * @since 4.4.0 */ public Promise<Project[]> getWorkspaceProjects() { return ps.getProjects().then((Function<List<ProjectConfigDto>, Project[]>)dtoConfigs -> { store.clear(); if (dtoConfigs.isEmpty()) { cachedConfigs = new ProjectConfigDto[0]; return NO_PROJECTS; } cachedConfigs = dtoConfigs.toArray(new ProjectConfigDto[dtoConfigs.size()]); Project[] projects = NO_PROJECTS; for (ProjectConfigDto config : dtoConfigs) { if (Path.valueOf(config.getPath()).segmentCount() == 1) { final Project project = resourceFactory.newProjectImpl(config, ResourceManager.this); store.register(project); final Optional<ProblemProjectMarker> optionalMarker = getProblemMarker(config); if (optionalMarker.isPresent()) { project.addMarker(optionalMarker.get()); } Project[] tmpProjects = copyOf(projects, projects.length + 1); tmpProjects[projects.length] = project; projects = tmpProjects; } } /* We need to guarantee that list of projects would be sorted by the logic provided in compareTo method implementation. */ java.util.Arrays.sort(projects); for (Project project : projects) { eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(project, ADDED | DERIVED))); } return projects; }); } /** * Returns the workspace root container. This container is a holder which may contains only {@link Project}s. * * @return the workspace container * @see Container * @since 4.4.0 */ public Container getWorkspaceRoot() { return workspaceRoot; } /** * Update state of specific properties in project and save this state on the server. * As the result method should return the {@link Promise} with new {@link Project} object. * <p/> * During the update method have to iterate on children of updated resource and if any of * them has changed own type, e.g. folder -> project, project -> folder, specific event * has to be fired. * <p/> * Method is not intended to be called in third party components. It is the service method * for {@link Project}. * * @param path * the path to project which should be updated * @param request * the update request * @return the {@link Promise} with new {@link Project} object. * @see ResourceChangedEvent * @see ProjectRequest * @see Project#update() * @since 4.4.0 */ protected Promise<Project> update(final Path path, final ProjectRequest request) { final ProjectConfig projectConfig = request.getBody(); final SourceStorage source = projectConfig.getSource(); final SourceStorageDto sourceDto = dtoFactory.createDto(SourceStorageDto.class); if (source != null) { sourceDto.setLocation(source.getLocation()); sourceDto.setType(source.getType()); sourceDto.setParameters(source.getParameters()); } final ProjectConfigDto dto = dtoFactory.createDto(ProjectConfigDto.class) .withName(projectConfig.getName()) .withPath(path.toString()) .withDescription(projectConfig.getDescription()) .withType(projectConfig.getType()) .withMixins(projectConfig.getMixins()) .withAttributes(projectConfig.getAttributes()) .withSource(sourceDto); return ps.updateProject(dto).thenPromise(reference -> { /* Note: After update, project may become to be other type, e.g. blank -> java or maven, or ant, or etc. And this may cause sub-project creations. Simultaneously on the client side there is outdated information about sub-projects, so we need to get updated project list. */ //dispose outdated resource final Optional<Resource> outdatedResource = store.getResource(path); checkState(outdatedResource.isPresent(), "Outdated resource wasn't found"); final Resource resource = outdatedResource.get(); checkState(resource instanceof Container, "Outdated resource is not a container"); Container container = (Container)resource; if (resource instanceof Folder) { Container parent = resource.getParent(); checkState(parent != null, "Parent of the resource wasn't found"); container = parent; } return synchronize(container).then(new Function<Resource[], Project>() { @Override public Project apply(Resource[] synced) throws FunctionException { final Optional<Resource> updatedProject = store.getResource(path); checkState(updatedProject.isPresent(), "Updated resource is not present"); checkState(updatedProject.get().isProject(), "Updated resource is not a project"); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(updatedProject.get(), UPDATED))); return (Project)updatedProject.get(); } }); }); } Promise<Folder> createFolder(final Container parent, final String name) { final Path path = Path.valueOf(name); return findResource(parent.getLocation().append(path), true).thenPromise(resource -> { if (resource.isPresent()) { return promises.reject(new IllegalStateException("Resource already exists")); } if (parent.getLocation().isRoot()) { return promises.reject(new IllegalArgumentException("Failed to create folder in workspace root")); } if (path.segmentCount() == 1 && !checkFolderName(name)) { return promises.reject(new IllegalArgumentException("Invalid folder name")); } return ps.createFolder(parent.getLocation().append(name)).thenPromise(reference -> { final Resource createdFolder = newResourceFrom(reference); store.register(createdFolder); return promises.resolve(createdFolder.asFolder()); }); }); } Promise<File> createFile(final Container parent, final String name, final String content) { if (!checkFileName(name)) { return promises.reject(new IllegalArgumentException("Invalid file name")); } return findResource(parent.getLocation().append(name), true).thenPromise(resource -> { if (resource.isPresent()) { return promises.reject(new IllegalStateException("Resource already exists")); } if (parent.getLocation().isRoot()) { return promises.reject(new IllegalArgumentException("Failed to create file in workspace root")); } return ps.createFile(parent.getLocation().append(name), content).thenPromise(reference -> { final Resource createdFile = newResourceFrom(reference); store.register(createdFile); return promises.resolve(createdFile.asFile()); }); }); } Promise<Project> createProject(final Project.ProjectRequest createRequest) { checkArgument(checkProjectName(createRequest.getBody().getName()), "Invalid project name"); checkArgument(typeRegistry.getProjectType(createRequest.getBody().getType()) != null, "Invalid project type"); final Path path = Path.valueOf(createRequest.getBody().getPath()); return findResource(path, true).thenPromise(resource -> { if (resource.isPresent()) { if (resource.get().isProject()) { throw new IllegalStateException("Project already exists"); } else if (resource.get().isFile()) { throw new IllegalStateException("File can not be converted to project"); } return update(path, createRequest); } final MutableProjectConfig projectConfig = (MutableProjectConfig)createRequest.getBody(); final List<NewProjectConfig> projectConfigList = projectConfig.getProjects(); projectConfigList.add(asDto(projectConfig)); final List<NewProjectConfigDto> configDtoList = asDto(projectConfigList); return ps.createBatchProjects(configDtoList).thenPromise( configList -> ps.getProjects().then((Function<List<ProjectConfigDto>, Project>)updatedConfiguration -> { //cache new configs cachedConfigs = updatedConfiguration.toArray(new ProjectConfigDto[updatedConfiguration.size()]); for (ProjectConfigDto projectConfigDto : configList) { if (projectConfigDto.getPath().equals(path.toString())) { final Project newResource = resourceFactory.newProjectImpl(projectConfigDto, ResourceManager.this); store.register(newResource); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(newResource, ADDED | DERIVED))); return newResource; } } throw new IllegalStateException("Created project is not found"); })); }); } private NewProjectConfigDto asDto(MutableProjectConfig config) { final SourceStorage source = config.getSource(); final SourceStorageDto sourceStorageDto = dtoFactory.createDto(SourceStorageDto.class) .withType(source.getType()) .withLocation(source.getLocation()) .withParameters(source.getParameters()); return dtoFactory.createDto(NewProjectConfigDto.class) .withName(config.getName()) .withPath(config.getPath()) .withDescription(config.getDescription()) .withSource(sourceStorageDto) .withType(config.getType()) .withMixins(config.getMixins()) .withAttributes(config.getAttributes()) .withOptions(config.getOptions()); } private List<NewProjectConfigDto> asDto(List<NewProjectConfig> configList) { List<NewProjectConfigDto> result = new ArrayList<>(configList.size()); for (NewProjectConfig config : configList) { final SourceStorage source = config.getSource(); final SourceStorageDto sourceStorageDto = dtoFactory.createDto(SourceStorageDto.class) .withType(source.getType()) .withLocation(source.getLocation()) .withParameters(source.getParameters()); result.add(dtoFactory.createDto(NewProjectConfigDto.class) .withName(config.getName()) .withPath(config.getPath()) .withDescription(config.getDescription()) .withSource(sourceStorageDto) .withType(config.getType()) .withMixins(config.getMixins()) .withAttributes(config.getAttributes()) .withOptions(config.getOptions())); } return result; } protected Promise<Project> importProject(final Project.ProjectRequest importRequest) { checkArgument(checkProjectName(importRequest.getBody().getName()), "Invalid project name"); checkNotNull(importRequest.getBody().getSource(), "Null source configuration occurred"); final Path path = Path.valueOf(importRequest.getBody().getPath()); return findResource(path, true).thenPromise(resource -> { final SourceStorage sourceStorage = importRequest.getBody().getSource(); final SourceStorageDto sourceStorageDto = dtoFactory.createDto(SourceStorageDto.class) .withType(sourceStorage.getType()) .withLocation(sourceStorage.getLocation()) .withParameters(sourceStorage.getParameters()); return ps.importProject(path, sourceStorageDto) .thenPromise(ignored -> ps.getProject(path).then((Function<ProjectConfigDto, Project>)config -> { cachedConfigs = add(cachedConfigs, config); Resource project = resourceFactory.newProjectImpl(config, ResourceManager.this); checkState(project != null, "Failed to locate imported project's configuration"); store.register(project); eventBus.fireEvent(new ResourceChangedEvent( new ResourceDeltaImpl(project, (resource.isPresent() ? UPDATED : ADDED) | DERIVED))); return (Project)project; })); }); } protected Promise<Resource> move(final Resource source, final Path destination, final boolean force) { checkArgument(!source.getLocation().isRoot(), "Workspace root is not allowed to be moved"); return findResource(destination, true).thenPromise(resource -> { checkState(!resource.isPresent() || force, "Cannot create '" + destination.toString() + "'. Resource already exists."); if (isResourceOpened(source)) { deletedFilesController.add(source.getLocation().toString()); } return clientServerEventService.sendFileTrackingSuspendEvent().thenPromise(success -> { store.dispose(source.getLocation(), !source.isFile()); //TODO: need to be tested return ps.move(source.getLocation(), destination.parent(), destination.lastSegment(), force) .thenPromise(ignored -> { if (source.isProject() && source.getLocation().segmentCount() == 1) { return ps.getProjects().then((Function<List<ProjectConfigDto>, Resource>)updatedConfigs -> { clientServerEventService.sendFileTrackingResumeEvent(); //cache new configs cachedConfigs = updatedConfigs.toArray(new ProjectConfigDto[updatedConfigs.size()]); store.dispose(source.getLocation(), true); for (ProjectConfigDto projectConfigDto : cachedConfigs) { if (projectConfigDto.getPath().equals(destination.toString())) { final Project newResource = resourceFactory.newProjectImpl(projectConfigDto, ResourceManager.this); store.register(newResource); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(newResource, source, ADDED | MOVED_FROM | MOVED_TO | DERIVED))); return newResource; } } throw new IllegalStateException("Resource not found"); }); } return findResource(destination, false).then((Function<Optional<Resource>, Resource>)movedResource -> { if (movedResource.isPresent()) { eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(movedResource.get(), source, ADDED | MOVED_FROM | MOVED_TO | DERIVED))); clientServerEventService.sendFileTrackingResumeEvent(); return movedResource.get(); } clientServerEventService.sendFileTrackingResumeEvent(); throw new IllegalStateException("Resource not found"); }); }); }); }); } protected Promise<Resource> copy(final Resource source, final Path destination, final boolean force) { checkArgument(!source.getLocation().isRoot(), "Workspace root is not allowed to be copied"); return findResource(destination, true).thenPromise(resource -> { if (resource.isPresent() && !force){ return promises.reject(new IllegalStateException("Cannot create '" + destination.toString() + "'. Resource already exists.")); } return ps.copy(source.getLocation(), destination.parent(), destination.lastSegment(), force) .thenPromise(ignored -> findResource(destination, false) .then((Function<Optional<Resource>, Resource>)copiedResource -> { if (copiedResource.isPresent()) { eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(copiedResource.get(), source, ADDED | COPIED_FROM | DERIVED))); return copiedResource.get(); } throw new IllegalStateException( "Resource not found"); })); }); } protected Promise<Void> delete(final Resource resource) { checkArgument(!resource.getLocation().isRoot(), "Workspace root is not allowed to be moved"); return ps.deleteItem(resource.getLocation()).then((Function<Void, Void>)ignored -> { Resource[] descToRemove = null; if (resource instanceof Container) { final Optional<Resource[]> optDescendants = store.getAll(resource.getLocation()); if (optDescendants.isPresent()) { descToRemove = optDescendants.get(); } } store.dispose(resource.getLocation(), !resource.isFile()); if (isResourceOpened(resource)) { deletedFilesController.add(resource.getLocation().toString()); } eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, REMOVED | DERIVED))); if (descToRemove != null) { for (Resource toRemove : descToRemove) { if (isResourceOpened(toRemove)) { deletedFilesController.add(toRemove.getLocation().toString()); } eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(toRemove, REMOVED | DERIVED))); } } return null; }); } protected Promise<Void> write(final File file, String content) { checkArgument(content != null, "Null content occurred"); return ps.setFileContent(file.getLocation(), content); } protected Promise<String> read(File file) { return ps.getFileContent(file.getLocation()); } Promise<Resource[]> getRemoteResources(final Container container, final int depth, boolean includeFiles) { checkArgument(depth > -2, "Invalid depth"); if (depth == DEPTH_ZERO) { return promises.resolve(NO_RESOURCES); } int depthToReload = depth; final Optional<Resource[]> descendants = store.getAll(container.getLocation()); if (depthToReload != -1 && descendants.isPresent()) { for (Resource resource : descendants.get()) { if (resource.getLocation().segmentCount() - container.getLocation().segmentCount() > depth) { depthToReload = resource.getLocation().segmentCount() - container.getLocation().segmentCount(); } } } return ps.getTree(container.getLocation(), depthToReload, includeFiles).then(new Function<TreeElement, Resource[]>() { @Override public Resource[] apply(TreeElement tree) throws FunctionException { class Visitor implements ResourceVisitor { Resource[] resources; private int size = 0; //size of total items private int incStep = 50; //step to increase resource array private Visitor() { this.resources = NO_RESOURCES; } @Override public void visit(Resource resource) { if (resource.isProject()) { inspectProject(resource.asProject()); } if (size > resources.length - 1) { //check load factor and increase resource array resources = copyOf(resources, resources.length + incStep); } resources[size++] = resource; } } final Visitor visitor = new Visitor(); traverse(tree, visitor); return copyOf(visitor.resources, visitor.size); } }).then((Function<Resource[], Resource[]>)reloaded -> { Resource[] result = new Resource[0]; if (descendants.isPresent()) { Resource[] outdated = descendants.get(); final Resource[] removed = removeAll(outdated, reloaded, false); for (Resource resource : removed) { store.dispose(resource.getLocation(), false); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, REMOVED))); } final Resource[] updated = removeAll(outdated, reloaded, true); for (Resource resource : updated) { store.register(resource); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, UPDATED))); final Optional<Resource> registered = store.getResource(resource.getLocation()); if (registered.isPresent()) { result = Arrays.add(result, registered.get()); } } final Resource[] added = removeAll(reloaded, outdated, false); for (Resource resource : added) { store.register(resource); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, ADDED))); final Optional<Resource> registered = store.getResource(resource.getLocation()); if (registered.isPresent()) { result = Arrays.add(result, registered.get()); } } } else { for (Resource resource : reloaded) { store.register(resource); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, ADDED))); final Optional<Resource> registered = store.getResource(resource.getLocation()); if (registered.isPresent()) { result = Arrays.add(result, registered.get()); } } } return result; }); } Promise<Optional<Container>> getContainer(final Path absolutePath) { return findResource(absolutePath, false).then((Function<Optional<Resource>, Optional<Container>>)optionalFolder -> { if (optionalFolder.isPresent()) { final Resource resource = optionalFolder.get(); checkState(resource instanceof Container, "Not a container"); return of((Container)resource); } return absent(); }); } protected Promise<Optional<File>> getFile(final Path absolutePath) { final Optional<Resource> resourceOptional = store.getResource(absolutePath); if (resourceOptional.isPresent() && resourceOptional.get().isFile()) { return promises.resolve(of(resourceOptional.get().asFile())); } if (store.getResource(absolutePath.parent()).isPresent()) { return findResource(absolutePath, true).thenPromise(optionalFile -> { if (optionalFile.isPresent()) { final Resource resource = optionalFile.get(); checkState(resource.getResourceType() == FILE, "Not a file"); return promises.resolve(of((File)resource)); } return promises.resolve(absent()); }); } else { return findResourceForExternalOperation(absolutePath, true).thenPromise(optionalFile -> { if (optionalFile.isPresent()) { final Resource resource = optionalFile.get(); checkState(resource.getResourceType() == FILE, "Not a file"); return promises.resolve(of((File)resource)); } return promises.resolve(absent()); }); } } Optional<Container> parentOf(Resource resource) { final Path parentLocation = resource.getLocation().segmentCount() == 1 ? Path.ROOT : resource.getLocation().parent(); final Optional<Resource> optionalParent = store.getResource(parentLocation); if (!optionalParent.isPresent()) { return absent(); } final Resource parentResource = optionalParent.get(); checkState(parentResource instanceof Container, "Parent resource is not a container"); return of((Container)parentResource); } Promise<Resource[]> childrenOf(final Container container, boolean forceUpdate) { if (forceUpdate) { return getRemoteResources(container, DEPTH_ONE, true); } final Optional<Resource[]> optChildren = store.get(container.getLocation()); if (optChildren.isPresent()) { return promises.resolve(optChildren.get()); } else { return promises.resolve(NO_RESOURCES); } } private Promise<Optional<Resource>> findResource(final Path absolutePath, boolean quiet) { return ps.getItem(absolutePath).thenPromise(itemReference -> { final Resource resource = newResourceFrom(itemReference); store.dispose(absolutePath, false); store.register(resource); if (resource.isProject()) { inspectProject(resource.asProject()); } return promises.resolve(fromNullable(resource)); }).catchErrorPromise(arg -> { if (!quiet) { throw new IllegalStateException(arg.getCause()); } return promises.resolve(absent()); }); } private Promise<Optional<Resource>> findResourceForExternalOperation(final Path absolutePath, boolean quiet) { Promise<Void> derived = promises.resolve(null); for (int i = absolutePath.segmentCount() - 1; i > 0; i--) { final Path pathToCache = absolutePath.removeLastSegments(i); derived = derived.thenPromise(arg -> loadAndRegisterResources(pathToCache)); } return derived.thenPromise(ignored -> findResource(absolutePath, quiet)); } private Promise<Void> loadAndRegisterResources(Path absolutePath) { return ps.getTree(absolutePath, 1, true).thenPromise(treeElement -> { final Optional<Resource[]> optionalChildren = store.get(absolutePath); if (optionalChildren.isPresent()) { for (Resource child : optionalChildren.get()) { store.dispose(child.getLocation(), false); } } for (TreeElement element : treeElement.getChildren()) { final Resource resource = newResourceFrom(element.getNode()); if (resource.isProject()) { inspectProject(resource.asProject()); } store.register(resource); } return promises.resolve(null); }); } private void inspectProject(Project project) { final Optional<ProjectConfigDto> optionalConfig = findProjectConfigDto(project.getLocation()); if (optionalConfig.isPresent()) { final Optional<ProblemProjectMarker> optionalMarker = getProblemMarker(optionalConfig.get()); if (optionalMarker.isPresent()) { project.addMarker(optionalMarker.get()); } } } private boolean isResourceOpened(final Resource resource) { if (!resource.isFile()) { return false; } File file = (File)resource; for (EditorPartPresenter editor : editorAgent.getOpenedEditors()) { Path editorPath = editor.getEditorInput().getFile().getLocation(); if (editorPath.equals(file.getLocation())) { return true; } } return false; } private void traverse(TreeElement tree, ResourceVisitor visitor) { for (final TreeElement element : tree.getChildren()) { final Resource resource = newResourceFrom(element.getNode()); visitor.visit(resource); if (resource instanceof Container) { traverse(element, visitor); } } } private Resource newResourceFrom(final ItemReference reference) { final Path path = Path.valueOf(reference.getPath()); switch (reference.getType()) { case "file": final Link link = reference.getLink(GET_CONTENT_REL); return resourceFactory.newFileImpl(path, link.getHref(), this); case "folder": return resourceFactory.newFolderImpl(path, this); case "project": final Optional<ProjectConfigDto> config = findProjectConfigDto(path); if (config.isPresent()) { return resourceFactory.newProjectImpl(config.get(), this); } else { return resourceFactory.newFolderImpl(path, this); } default: throw new IllegalArgumentException("Failed to recognize resource type to create."); } } private Optional<ProjectConfigDto> findProjectConfigDto(final Path path) { for (ProjectConfigDto config : cachedConfigs) { if (Path.valueOf(config.getPath()).equals(path)) { return of(config); } } return absent(); } private Optional<ProblemProjectMarker> getProblemMarker(ProjectConfigDto projectConfigDto) { List<ProjectProblemDto> problems = projectConfigDto.getProblems(); if (problems == null || problems.isEmpty()) { return absent(); } Map<Integer, String> code2Message = new HashMap<>(problems.size()); for (ProjectProblemDto problem : problems) { code2Message.put(problem.getCode(), problem.getMessage()); } return of(new ProblemProjectMarker(code2Message)); } protected Promise<Resource[]> synchronize(final Container container) { return ps.getProjects().thenPromise(updatedConfiguration -> { cachedConfigs = updatedConfiguration.toArray(new ProjectConfigDto[updatedConfiguration.size()]); int maxDepth = 1; final Optional<Resource[]> descendants = store.getAll(container.getLocation()); if (descendants.isPresent()) { final Resource[] resources = descendants.get(); for (Resource resource : resources) { final int segCount = resource.getLocation().segmentCount() - container.getLocation().segmentCount(); if (segCount > maxDepth) { maxDepth = segCount; } } } final Container[] holder = new Container[]{container}; if (holder[0].isProject()) { final Optional<ProjectConfigDto> config = findProjectConfigDto(holder[0].getLocation()); if (config.isPresent()) { final ProjectImpl project = resourceFactory.newProjectImpl(config.get(), ResourceManager.this); store.register(project); holder[0] = project; } } return getRemoteResources(holder[0], maxDepth, true).then((Function<Resource[], Resource[]>)resources -> { eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(holder[0], SYNCHRONIZED | DERIVED))); return resources; }); }); } protected Promise<ResourceDelta[]> synchronize(final ResourceDelta[] deltas) { Promise<Void> promise = promises.resolve(null); for (final ResourceDelta delta : deltas) { if (delta.getKind() == ADDED) { if (delta.getFlags() == (MOVED_FROM | MOVED_TO)) { promise.thenPromise(ignored -> onExternalDeltaMoved(delta)); } else { promise.thenPromise(ignored -> onExternalDeltaAdded(delta)); } } else if (delta.getKind() == REMOVED) { promise.thenPromise(ignored -> onExternalDeltaRemoved(delta)); } else if (delta.getKind() == UPDATED) { promise.thenPromise(ignored -> onExternalDeltaUpdated(delta)); } } return promise.thenPromise(ignored -> promises.resolve(deltas)); } private Promise<Void> onExternalDeltaMoved(final ResourceDelta delta) { final Optional<Resource> toRemove = store.getResource(delta.getFromPath()); store.dispose(delta.getFromPath(), true); return findResourceForExternalOperation(delta.getToPath(), true).thenPromise(resource -> { if (resource.isPresent() && toRemove.isPresent()) { Resource intercepted = resource.get(); if (!store.getResource(intercepted.getLocation()).isPresent()) { store.register(intercepted); } eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(intercepted, toRemove.get(), ADDED | MOVED_FROM | MOVED_TO | DERIVED))); } return promises.resolve(null); }); } private Promise<Void> onExternalDeltaAdded(final ResourceDelta delta) { if (delta.getToPath().segmentCount() == 1) { return ps.getProjects().thenPromise(updatedConfiguration -> { cachedConfigs = updatedConfiguration.toArray(new ProjectConfigDto[updatedConfiguration.size()]); for (ProjectConfigDto config : cachedConfigs) { if (Path.valueOf(config.getPath()).equals(delta.getToPath())) { final Project project = resourceFactory.newProjectImpl(config, ResourceManager.this); store.register(project); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(project, ADDED))); } } return promises.resolve(null); }); } return findResourceForExternalOperation(delta.getToPath(), true).thenPromise(resource -> { if (resource.isPresent()) { Resource intercepted = resource.get(); if (!store.getResource(intercepted.getLocation()).isPresent()) { store.register(intercepted); } eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(intercepted, ADDED | DERIVED))); } return promises.resolve(null); }); } private Promise<Void> onExternalDeltaUpdated(final ResourceDelta delta) { if (delta.getToPath().segmentCount() == 0) { workspaceRoot.synchronize(); return null; } return findResourceForExternalOperation(delta.getToPath(), true).thenPromise(resource -> { if (resource.isPresent()) { eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource.get(), UPDATED | DERIVED))); } return promises.resolve(null); }); } private Promise<Void> onExternalDeltaRemoved(final ResourceDelta delta) { final Optional<Resource> resourceOptional = store.getResource(delta.getFromPath()); if (resourceOptional.isPresent()) { final Resource resource = resourceOptional.get(); store.dispose(resource.getLocation(), true); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, REMOVED | DERIVED))); } return promises.resolve(null); } protected Promise<Resource[]> search(final Container container, String fileMask, String contentMask) { QueryExpression queryExpression = new QueryExpression(); if (!isNullOrEmpty(contentMask)) { queryExpression.setText(contentMask); } if (!isNullOrEmpty(fileMask)) { queryExpression.setName(fileMask); } if (!container.getLocation().isRoot()) { queryExpression.setPath(container.getLocation().toString()); } return ps.search(queryExpression).thenPromise(references -> { if (references.isEmpty()) { return promises.resolve(NO_RESOURCES); } int maxDepth = 0; final Path[] paths = new Path[references.size()]; for (int i = 0; i < paths.length; i++) { final Path path = Path.valueOf(references.get(i).getPath()); paths[i] = path; if (path.segmentCount() > maxDepth) { maxDepth = path.segmentCount(); } } return getRemoteResources(container, maxDepth, true).then((Function<Resource[], Resource[]>)resources -> { Resource[] filtered = NO_RESOURCES; Path[] mutablePaths = paths; outer: for (Resource resource : resources) { if (resource.getResourceType() != FILE) { continue; } for (int i = 0; i < mutablePaths.length; i++) { Path path = mutablePaths[i]; if (path.segmentCount() == resource.getLocation().segmentCount() && path.equals(resource.getLocation())) { Resource[] tmpFiltered = copyOf(filtered, filtered.length + 1); tmpFiltered[filtered.length] = resource; filtered = tmpFiltered; //reduce the size of mutablePaths by removing already checked item int size = mutablePaths.length; int numMoved = mutablePaths.length - i - 1; if (numMoved > 0) { arraycopy(mutablePaths, i + 1, mutablePaths, i, numMoved); } mutablePaths = copyOf(mutablePaths, --size); continue outer; } } } return filtered; }); }); } Promise<SourceEstimation> estimate(Container container, String projectType) { checkArgument(projectType != null, "Null project type"); checkArgument(!projectType.isEmpty(), "Empty project type"); return ps.estimate(container.getLocation(), projectType); } void notifyMarkerChanged(Resource resource, Marker marker, int status) { eventBus.fireEvent(new MarkerChangedEvent(resource, marker, status)); eventBus.fireEvent(new ResourceChangedEvent(new ResourceDeltaImpl(resource, UPDATED))); } protected String getUrl(Resource resource) { checkArgument(!resource.getLocation().isRoot(), "Workspace root doesn't have export URL"); final String baseUrl = devMachine.getWsAgentBaseUrl() + "/project/export"; if (resource.getResourceType() == FILE) { return baseUrl + "/file" + resource.getLocation(); } return urlModifier.modify(baseUrl + resource.getLocation()); } public Promise<List<SourceEstimation>> resolve(Project project) { return ps.resolveSources(project.getLocation()); } interface ResourceVisitor { void visit(Resource resource); } public interface ResourceFactory { ProjectImpl newProjectImpl(ProjectConfig reference, ResourceManager resourceManager); FolderImpl newFolderImpl(Path path, ResourceManager resourceManager); FileImpl newFileImpl(Path path, String contentUrl, ResourceManager resourceManager); } public interface ResourceManagerFactory { ResourceManager newResourceManager(DevMachine devMachine); } }