/*******************************************************************************
* Copyright (c) 2006, 2015 Wind River Systems and others.
* 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.examples.dsf.filebrowser;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
/**
* File view model node which returns file elements that are found in the directory
* specified by the parent element. The child nodes of this node are fixed to
* reference this element, and therefore this node will recursively populate
* the contents of the tree reflecting the underlying filesystem directories.
* <br>
* Note: this node does NOT sub-class the {@link org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMNode}
*/
@SuppressWarnings("restriction")
class FileVMNode
implements IElementLabelProvider, IVMNode
{
/**
* Reference to the viewer model provider. It's mainly used to access the
* viewer model adapter and its executor.
*/
private final FileBrowserVMProvider fProvider;
public FileVMNode(FileBrowserVMProvider provider) {
fProvider = provider;
}
@Override
public String toString() {
return "FileVMNode";
}
@Override
public void dispose() {
// All resources garbage collected.
}
public void setChildNodes(IVMNode[] childNodes) {
throw new UnsupportedOperationException("This node does not support children."); //$NON-NLS-1$
}
/**
* List of child nodes containing only a reference to this.
*/
private final IVMNode[] fChildNodes = { this };
public IVMNode[] getChildNodes() {
return fChildNodes;
}
@Override
public void update(final IHasChildrenUpdate[] updates) {
new Job("") { //$NON-NLS-1$
{
setSystem(true);
setPriority(INTERACTIVE);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (IHasChildrenUpdate update : updates) {
/*
* Do not retrieve directory contents just to mark the plus
* sign in the tree. If it's a directory, just assume that
* it has children.
*/
FileVMContext vmc = (FileVMContext)update.getElement();
update.setHasChilren(vmc.getFile().isDirectory());
update.done();
}
return Status.OK_STATUS;
}
}.schedule();
}
@Override
public void update(final IChildrenCountUpdate[] updates) {
new Job("") { //$NON-NLS-1$
{
setSystem(true);
setPriority(INTERACTIVE);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (IChildrenCountUpdate update : updates) {
update.setChildCount(getFiles(update).length);
update.done();
}
return Status.OK_STATUS;
}
}.schedule();
}
@Override
public void update(final IChildrenUpdate[] updates) {
new Job("") { //$NON-NLS-1$
{
setSystem(true);
setPriority(INTERACTIVE);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (IChildrenUpdate update : updates) {
File[] files = getFiles(update);
int offset = update.getOffset() != -1 ? update.getOffset() : 0;
int length = update.getLength() != -1 ? update.getLength() : files.length;
for (int i = offset; (i < files.length) && (i < (offset + length)); i++) {
update.setChild(new FileVMContext(FileVMNode.this, files[i]), i);
}
update.done();
}
return Status.OK_STATUS;
}
}.schedule();
}
@Override
public void update(final ILabelUpdate[] updates) {
new Job("") { //$NON-NLS-1$
{
setSystem(true);
setPriority(INTERACTIVE);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
for (ILabelUpdate update : updates) {
update.setLabel(getLabel((FileVMContext)update.getElement()), 0);
update.done();
}
return Status.OK_STATUS;
}
}.schedule();
}
private static final File[] EMPTY_FILE_LIST = new File[0];
/**
* Retrieves the list of files for this node. The list of files is based
* on the parent element in the tree, which must be of type FileVMC.
*
* @param update Update object containing the path (and the parent element)
* in the tree viewer.
* @return List of files contained in the directory specified in the
* update object. An empty list if the parent element is not a directory.
* @throws ClassCastException If the parent element contained in the update
* is NOT of type FileVMC.
*/
private File[] getFiles(IViewerUpdate update) {
FileVMContext vmc = (FileVMContext)update.getElement();
File[] files = vmc.getFile().listFiles();
return files != null ? files : EMPTY_FILE_LIST;
}
/**
* Returs the text label to show in the tree for given element.
*/
private String getLabel(FileVMContext vmc) {
return vmc.getFile().getName();
}
@Override
public void getContextsForEvent(VMDelta parentDelta, Object event, DataRequestMonitor<IVMContext[]> rm) {
rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.NOT_SUPPORTED, "", null)); //$NON-NLS-1$
rm.done();
}
@Override
public int getDeltaFlags(Object e) {
/*
* @see buildDelta()
*/
int retVal = IModelDelta.NO_CHANGE;
if (e instanceof String) {
retVal |= IModelDelta.SELECT | IModelDelta.EXPAND;
}
return retVal;
}
@Override
public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
/*
* The FileLayoutNode is recursive, with itself as the only child. In this
* method the delta is calculated for a full path VMContext elements, and the
* implementation of this method is not recursive.
*/
if (event instanceof String) {
new Job("") { //$NON-NLS-1$
{
setSystem(true);
setPriority(INTERACTIVE);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
/*
* Requirements for a selection event to be issued is that the file exist, and
* that the parentDelta contain a FileVMC of a parent directory as its element.
*
* The test for first the former requirement could be performed inside getDeltaFlags()
* but getDeltaFlags() is synchronous, so it is better to perform this test here using
* a background thread (job).
*
* The latter is requirement is needed because this node does not have the algorithm
* calculate the complete list of root nodes. That algorithm is implemented inside the
* {@link FileSystemRootsLayoutNode#updateElements} method.
*/
final File eventFile = new File((String)event);
File parentFile = null;
if (parentDelta.getElement() instanceof FileVMContext) {
parentFile = ((FileVMContext)parentDelta.getElement()).getFile();
}
// The file has to exist in order for us to be able to select
// it in the tree.
if (eventFile.exists() && parentFile != null) {
// Create a list containing all files in path
List<File> filePath = new LinkedList<File>();
for (File file = eventFile; file != null && !file.equals(parentFile); file = file.getParentFile()) {
filePath.add(0, file);
}
if (filePath.size() != 0) {
// Build the delta for all files in path.
ModelDelta delta = parentDelta;
File[] allFilesInDirectory = parentFile.listFiles();
for (File pathSegment : filePath) {
// All files in path should be directories, and should therefore
// have a valid list of elements.
assert allFilesInDirectory != null;
File[] pathSegmentDirectoryFiles = pathSegment.listFiles();
delta = delta.addNode(
new FileVMContext(FileVMNode.this, pathSegment),
nodeOffset + Arrays.asList(allFilesInDirectory).indexOf(pathSegment),
IModelDelta.NO_CHANGE,
pathSegmentDirectoryFiles != null ? pathSegmentDirectoryFiles.length : 0);
allFilesInDirectory = pathSegmentDirectoryFiles;
}
// The last file in path gets the EXPAND | SELECT flags.
delta.setFlags(delta.getFlags() | IModelDelta.SELECT | IModelDelta.EXPAND);
}
}
// Invoke the request monitor.
requestMonitor.done();
return Status.OK_STATUS;
}
}.schedule();
} else {
requestMonitor.done();
}
}
/**
* Override the behavior which checks for delta flags of all the child nodes,
* because we would get stuck in a recursive loop. Instead call only the child
* nodes which are not us.
*/
protected Map<IVMNode, Integer> getChildNodesWithDeltas(Object e) {
Map<IVMNode, Integer> nodes = new HashMap<IVMNode, Integer>();
for (final IVMNode childNode : getChildNodes()) {
int delta = childNode.getDeltaFlags(e);
if (delta != IModelDelta.NO_CHANGE) {
nodes.put(childNode, delta);
}
}
return nodes;
}
@Override
public IVMProvider getVMProvider() {
return fProvider;
}
}