/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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 org.jkiss.dbeaver.model.navigator;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBIcon;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.app.DBPResourceHandler;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* DBNResource
*/
public class DBNResource extends DBNNode// implements IContributorResourceAdapter
{
private static final Log log = Log.getLog(DBNResource.class);
private static final DBNNode[] EMPTY_NODES = new DBNNode[0];
private static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(DBConstants.DEFAULT_TIMESTAMP_FORMAT);
private IResource resource;
private DBPResourceHandler handler;
private DBNNode[] children;
private DBPImage resourceImage;
public DBNResource(DBNNode parentNode, IResource resource, DBPResourceHandler handler)
{
super(parentNode);
this.resource = resource;
this.handler = handler;
}
@Override
protected void dispose(boolean reflect)
{
if (this.handler != null) {
if (children != null) {
for (DBNNode child : children) {
child.dispose(reflect);
}
children = null;
}
if (reflect) {
getModel().fireNodeEvent(new DBNEvent(this, DBNEvent.Action.REMOVE, this));
}
this.resource = null;
this.handler = null;
}
super.dispose(reflect);
}
public int getFeatures()
{
return handler == null ? 0 : handler.getFeatures(resource);
}
@Override
public String getNodeType()
{
return handler == null ? null :handler.getTypeName(resource);
}
@Override
@Property(viewable = true, order = 1)
public String getNodeName()
{
if (resource == null || handler == null) {
return null;
}
return handler.getResourceNodeName(resource);
// if (resource instanceof IFile) {
//
// }
// return resource.getFullPath().lastSegment();
}
@Override
// @Property(viewable = false, order = 100)
public String getNodeDescription()
{
return handler == null || resource == null ? null : handler.getResourceDescription(resource);
}
@Override
public DBPImage getNodeIcon()
{
if (resourceImage != null) {
return resourceImage;
}
/*
if (resource instanceof IFile) {
final IContentDescription contentDescription = ((IFile) resource).getContentDescription();
contentDescription.getContentType().
}
*/
if (resource == null) {
return null;
}
switch (resource.getType()) {
case IResource.FOLDER: return resource.isLinked() ? DBIcon.TREE_FOLDER_LINK : DBIcon.TREE_FOLDER;
case IResource.PROJECT: return DBIcon.PROJECT;
default: return DBIcon.TREE_PAGE;
}
}
@Override
public boolean allowsChildren()
{
return resource instanceof IContainer;
}
@Override
public DBNNode[] getChildren(DBRProgressMonitor monitor) throws DBException
{
if (children == null) {
if (resource instanceof IContainer) {
this.children = readChildNodes(monitor);
}
}
return children;
}
protected DBNNode[] readChildNodes(DBRProgressMonitor monitor) throws DBException
{
List<DBNNode> result = new ArrayList<>();
try {
IResource[] members = ((IContainer) resource).members(false);
for (IResource member : members) {
DBNNode newChild = makeNode(member);
if (newChild != null) {
result.add(newChild);
}
}
} catch (CoreException e) {
throw new DBException("Can't read container's members", e);
}
if (result.isEmpty()) {
return EMPTY_NODES;
} else {
filterChildren(result);
final DBNNode[] childNodes = result.toArray(new DBNNode[result.size()]);
sortChildren(childNodes);
return childNodes;
}
}
DBNResource getChild(IResource resource)
{
if (children == null) {
return null;
}
for (DBNNode child : children) {
if (child instanceof DBNResource && resource.equals(((DBNResource)child).getResource())) {
return (DBNResource) child;
}
}
return null;
}
private DBNNode makeNode(IResource resource)
{
// if (resource.isHidden()) {
// return null;
// }
if (resource.getParent() instanceof IProject && resource.getName().startsWith(".")) {
// Skip project config
return null;
}
try {
if (resource instanceof IFolder && resource.getParent() instanceof IFolder) {
// Sub folder
return handler.makeNavigatorNode(this, resource);
}
DBPResourceHandler resourceHandler = getModel().getPlatform().getProjectManager().getResourceHandler(resource);
if (resourceHandler == null) {
log.debug("Skip resource '" + resource.getName() + "'");
return null;
}
return resourceHandler.makeNavigatorNode(this, resource);
} catch (Exception e) {
log.error("Error creating navigator node for resource '" + resource.getName() + "'", e);
return null;
}
}
@Override
public boolean isManagable()
{
return true;
}
@Override
public DBNNode refreshNode(DBRProgressMonitor monitor, Object source) throws DBException
{
try {
resource.refreshLocal(IResource.DEPTH_INFINITE, monitor.getNestedMonitor());
// FIXME: seems to be a bug in Eclipse. Some resources are "stuck" in workspace
// FIXME: although they do not exist on physical file-system.
// FIXME: The only workaround is to check real file and drop resource by force
if (!resource.getLocation().toFile().exists()) {
log.warn("Resource '" + resource.getName() + "' doesn't exists on file system: deleted in workspace");
resource.delete(true, monitor.getNestedMonitor());
}
} catch (CoreException e) {
throw new DBException("Can't refresh resource", e);
}
return this;
}
@Override
public String getNodeItemPath() {
StringBuilder pathName = new StringBuilder();
for (DBNNode node = this; node instanceof DBNResource; node = node.getParentNode()) {
if (pathName.length() > 0) {
pathName.insert(0, '/');
}
pathName.insert(0, ((DBNResource) node).getResource().getName());
}
return NodePathType.resource.getPrefix() + pathName.toString();
}
@Override
public boolean supportsRename()
{
return (getFeatures() & DBPResourceHandler.FEATURE_RENAME) != 0;
}
@Override
public void rename(DBRProgressMonitor monitor, String newName) throws DBException
{
try {
if (newName.indexOf('.') == -1 && resource instanceof IFile) {
String ext = resource.getFileExtension();
if (!CommonUtils.isEmpty(ext)) {
newName += "." + ext;
}
}
if (!newName.equals(resource.getName())) {
resource.move(resource.getParent().getFullPath().append(newName), true, monitor.getNestedMonitor());
}
} catch (CoreException e) {
throw new DBException("Can't rename resource", e);
}
}
@Override
public boolean supportsDrop(DBNNode otherNode)
{
if (!(resource instanceof IFolder) || (getFeatures() & DBPResourceHandler.FEATURE_MOVE_INTO) == 0) {
return false;
}
if (otherNode == null) {
// Potentially any other node could be dropped in the folder
return true;
}
// Drop supported only if both nodes are resource with the same handler and DROP feature is supported
return otherNode instanceof DBNResource
&& otherNode != this
&& otherNode.getParentNode() != this
&& !this.isChildOf(otherNode)
&& ((DBNResource)otherNode).handler == this.handler;
}
@Override
public void dropNodes(Collection<DBNNode> nodes) throws DBException
{
for (DBNNode node : nodes) {
DBNResource resourceNode = (DBNResource) node;
IResource otherResource = resourceNode.getResource();
if (otherResource != null) {
try {
otherResource.move(
resource.getFullPath().append(otherResource.getName()),
true,
new NullProgressMonitor());
} catch (CoreException e) {
throw new DBException("Can't delete resource", e);
}
}
}
}
@Nullable
public IResource getResource()
{
return resource;
}
protected void filterChildren(List<DBNNode> list)
{
}
protected void sortChildren(DBNNode[] list)
{
Arrays.sort(list, new Comparator<DBNNode>() {
@Override
public int compare(DBNNode o1, DBNNode o2)
{
if (o1 instanceof DBNProjectDatabases) {
return -1;
} else if (o2 instanceof DBNProjectDatabases) {
return 1;
} else {
if (o1 instanceof DBNResource && o2 instanceof DBNResource) {
IResource res1 = ((DBNResource)o1).getResource();
IResource res2 = ((DBNResource)o2).getResource();
if (res1 instanceof IFolder && !(res2 instanceof IFolder)) {
return -1;
} else if (res2 instanceof IFolder && !(res1 instanceof IFolder)) {
return 1;
}
}
return o1.getNodeName().compareToIgnoreCase(o2.getNodeName());
}
}
});
}
public void setResourceImage(DBPImage resourceImage)
{
this.resourceImage = resourceImage;
}
public void createNewFolder(String folderName)
throws DBException
{
if (resource instanceof IFolder) {
IFolder newFolder = ((IFolder) resource).getFolder(folderName);
if (newFolder.exists()) {
throw new DBException("Folder '" + folderName + "' already exists in '" + resource.getFullPath().toString() + "'");
}
try {
newFolder.create(true, true, new NullProgressMonitor());
} catch (CoreException e) {
throw new DBException("Can't create new folder", e);
}
}
}
public Collection<DBPDataSourceContainer> getAssociatedDataSources()
{
return handler == null ? null : handler.getAssociatedDataSources(resource);
}
public void refreshResourceState(Object source) {
DBPResourceHandler newHandler = getModel().getPlatform().getProjectManager().getResourceHandler(resource);
if (newHandler != handler) {
handler = newHandler;
}
handler.updateNavigatorNode(this, resource);
getModel().fireNodeEvent(new DBNEvent(source, DBNEvent.Action.UPDATE, this));
}
protected void handleResourceChange(IResourceDelta delta)
{
if (delta.getKind() == IResourceDelta.CHANGED) {
// Update this node in navigator
refreshResourceState(delta);
}
if (children == null) {
// Child nodes are not yet read so nothing to change here - just return
return;
}
//delta.getAffectedChildren(IResourceDelta.ALL_WITH_PHANTOMS, IContainer.INCLUDE_HIDDEN)
for (IResourceDelta childDelta : delta.getAffectedChildren(IResourceDelta.ALL_WITH_PHANTOMS, IContainer.INCLUDE_HIDDEN)) {
handleChildResourceChange(childDelta);
}
}
protected void handleChildResourceChange(IResourceDelta delta) {
final IResource deltaResource = delta.getResource();
DBNResource childResource = getChild(deltaResource);
if (childResource == null) {
if (delta.getKind() == IResourceDelta.ADDED || delta.getKind() == IResourceDelta.CHANGED) {
// New child or new "grand-child"
DBNNode newChild = makeNode(deltaResource);
if (newChild != null) {
children = ArrayUtils.add(DBNNode.class, children, newChild);
sortChildren(children);
getModel().fireNodeEvent(new DBNEvent(delta, DBNEvent.Action.ADD, newChild));
if (delta.getKind() == IResourceDelta.CHANGED) {
// Notify just created resource
// This may happen (e.g.) when first script created in just created script folder
childResource = getChild(deltaResource);
if (childResource != null) {
childResource.handleResourceChange(delta);
}
}
}
} else {
//log.warn("Can't find resource '" + childDelta.getResource().getName() + "' in navigator model");
}
} else {
if (delta.getKind() == IResourceDelta.REMOVED) {
// Node deleted
children = ArrayUtils.remove(DBNNode.class, children, childResource);
childResource.dispose(true);
} else {
// Node changed - handle it recursive
childResource.handleResourceChange(delta);
}
}
}
@Property(viewable = true, order = 10)
public String getResourcePath() {
return resource == null ? "" : resource.getFullPath().toOSString();
}
@Property(viewable = false, order = 11)
public String getResourceLocation() {
return resource == null ? "" : resource.getLocation().toOSString();
}
@Property(viewable = true, order = 11)
public long getResourceSize() {
return resource == null ? 0 : resource.getLocation().toFile().length();
}
@Property(viewable = true, order = 11)
public String getResourceLastModified() {
return resource == null ? null : DATE_FORMAT.format(resource.getLocation().toFile().lastModified());
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (resource != null && adapter.isAssignableFrom(resource.getClass())) {
return adapter.cast(resource);
}
return super.getAdapter(adapter);
}
/*
@Override
public IResource getAdaptedResource(IAdaptable adaptable) {
if (adaptable instanceof DBNResource) {
return ((DBNResource) adaptable).resource;
}
return null;
}
*/
@Override
public String toString() {
return resource == null ? super.toString() : resource.toString();
}
}