]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.ui/src/org/simantics/ui/workbench/dialogs/ResourceLabelProvider.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.ui / src / org / simantics / ui / workbench / dialogs / ResourceLabelProvider.java
index 704138d2af6e7627eae9a0ba6d8f7c878c17be1f..e2f70e7c7d1d50197a28e01fb4873476e6277fa0 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2013 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *     Semantum Oy - index based searching (#4255)\r
- *******************************************************************************/\r
-package org.simantics.ui.workbench.dialogs;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-\r
-import java.util.ArrayList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.concurrent.CopyOnWriteArrayList;\r
-import java.util.concurrent.atomic.AtomicBoolean;\r
-\r
-import org.eclipse.core.runtime.IProgressMonitor;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.MultiStatus;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.core.runtime.jobs.Job;\r
-import org.eclipse.jface.resource.DeviceResourceManager;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.jface.resource.ResourceManager;\r
-import org.eclipse.jface.viewers.ILabelProvider;\r
-import org.eclipse.jface.viewers.ILabelProviderListener;\r
-import org.eclipse.jface.viewers.LabelProviderChangedEvent;\r
-import org.eclipse.swt.graphics.Device;\r
-import org.eclipse.swt.graphics.Image;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.ui.progress.IProgressConstants;\r
-import org.simantics.DatabaseJob;\r
-import org.simantics.Simantics;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.ui.icons.ImageDescriptorProvider;\r
-import org.simantics.ui.icons.ImageUtil;\r
-import org.simantics.ui.internal.Activator;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-import org.simantics.utils.threads.SWTThread;\r
-import org.simantics.utils.ui.gfx.CompositionImageDescriptor;\r
-\r
-/**\r
- * ResourceLabelProvider is a label provider for Simantics database\r
- * {@link Resource}s.\r
- * \r
- * <p>\r
- * This label provider utilizes {@link ImageDescriptorProvider} adapters for\r
- * determining the icon to use.\r
- * \r
- * @author Toni Kalajainen\r
- * @author Tuukka Lehtonen\r
- * \r
- * @see Resource\r
- * @see ILabelProvider\r
- * @see ILabelProviderListener\r
- */\r
-public class ResourceLabelProvider implements ILabelProvider {\r
-\r
-    protected List<ILabelProviderListener> listeners = null;\r
-    protected IThreadWorkQueue thread;\r
-    protected TaskRepository tasks;\r
-    protected LoadJob loadJob;\r
-\r
-    public ResourceLabelProvider(Display display) {\r
-        this.thread = SWTThread.getThreadAccess(display);\r
-        this.tasks = new TaskRepository(display);\r
-        this.loadJob = new LoadJob("Resource Labeler", tasks, this);\r
-    }\r
-\r
-    public void fireChangedEvent(LabelProviderChangedEvent e) {\r
-        ILabelProviderListener[] listeners;\r
-        synchronized(this) {\r
-            if (this.listeners == null)\r
-                return;\r
-            listeners = this.listeners.toArray(new ILabelProviderListener[0]);\r
-        }\r
-        for (ILabelProviderListener l : listeners)\r
-            l.labelProviderChanged(e);\r
-    }\r
-\r
-    public void asyncFireChangedEvent(final LabelProviderChangedEvent e) {\r
-        thread.asyncExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                fireChangedEvent(e);\r
-            }\r
-        });\r
-    }\r
-\r
-    public synchronized void addListener(ILabelProviderListener listener) {\r
-        if (listeners == null)\r
-            listeners = new CopyOnWriteArrayList<ILabelProviderListener>();\r
-        listeners.add(listener);\r
-    }\r
-\r
-    public void dispose() {\r
-        if (listeners != null)\r
-            listeners.clear();\r
-        loadJob.dispose();\r
-        tasks.dispose();\r
-    }\r
-\r
-    public void clear() {\r
-        tasks.clear();\r
-    }\r
-\r
-    public boolean isLabelProperty(Object element, String property) {\r
-        System.out.println("isLabelProperty(" + element + ", " + property + ")");\r
-        return false;\r
-    }\r
-\r
-    public synchronized void removeListener(ILabelProviderListener listener) {\r
-        if (listeners != null)\r
-            listeners.remove(listener);\r
-    }\r
-\r
-    @Override\r
-    public Image getImage(Object element) {\r
-        if (element == null)\r
-            return null;\r
-        Resource r = (Resource) element;\r
-        Task task = tasks.getCompleted(r);\r
-        if (task != null) {\r
-            return task.image;\r
-        }\r
-        task = tasks.queue(r);\r
-        loadJob.scheduleIfNecessary(100);\r
-        return task.image;\r
-    }\r
-\r
-    @Override\r
-    public String getText(Object element) {\r
-        if (element == null)\r
-            return null;\r
-        Resource r = (Resource) element;\r
-        Task task = tasks.getCompleted(r);\r
-        if (task != null) {\r
-            return task.label;\r
-        }\r
-        task = tasks.queue(r);\r
-        loadJob.scheduleIfNecessary(100);\r
-        return task.label;\r
-    }\r
-\r
-    private static class LoadJob extends DatabaseJob {\r
-        private TaskRepository tasks;\r
-        private ResourceLabelProvider labelProvider;\r
-        private AtomicBoolean isScheduled = new AtomicBoolean();\r
-\r
-        public LoadJob(String name, TaskRepository tasks, ResourceLabelProvider labelProvider) {\r
-            super(name);\r
-            setUser(false);\r
-            setSystem(true);\r
-            setPriority(SHORT);\r
-            setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);\r
-            this.tasks = tasks;\r
-            this.labelProvider = labelProvider;\r
-        }\r
-\r
-        /**\r
-         * Schedules the job with {@link Job#schedule()} if necessary, i.e. if\r
-         * not already scheduled. This method avoids some unnecessary overhead\r
-         * caused by repeatedly invoking {@link Job#schedule()}.\r
-         * \r
-         * @return <code>true</code> if scheduled, <code>false</code> otherwise\r
-         */\r
-        public boolean scheduleIfNecessary(long delay) {\r
-            if (isScheduled.compareAndSet(false, true)) {\r
-                schedule(delay);\r
-                return true;\r
-            }\r
-            return false;\r
-        }\r
-\r
-        void dispose() {\r
-            tasks = null;\r
-            labelProvider = null;\r
-        }\r
-\r
-        @Override\r
-        public boolean shouldSchedule() {\r
-            return tasks != null;\r
-        }\r
-\r
-        @Override\r
-        public boolean shouldRun() {\r
-            return tasks != null;\r
-        }\r
-\r
-        @Override\r
-        protected IStatus run(IProgressMonitor monitor) {\r
-            try {\r
-                isScheduled.set(false);\r
-\r
-                TaskRepository tr = tasks;\r
-                if (tr == null)\r
-                    return Status.CANCEL_STATUS;\r
-\r
-                Task[] taskList = tr.pruneTasks();\r
-                return runTasks(tr, taskList);\r
-            } finally {\r
-                monitor.done();\r
-            }\r
-        }\r
-\r
-        private IStatus runTasks(final TaskRepository tr, final Task[] taskList) {\r
-            Session session = Simantics.peekSession();\r
-            if (session == null)\r
-                return Status.CANCEL_STATUS;\r
-            final ResourceManager rm = tr.resourceManager;\r
-            if (rm == null)\r
-                return Status.CANCEL_STATUS;\r
-\r
-            final MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems encountered while invoking resource label provider:", null);\r
-            try {\r
-                Simantics.getSession().syncRequest(new ReadRequest() {\r
-                    @Override\r
-                    public void run(ReadGraph graph) throws DatabaseException {\r
-                        for (Task task : taskList) {\r
-                            try {\r
-                                if (tr.isDisposed())\r
-                                    return;\r
-\r
-                                runTask(rm, graph, task);\r
-                                tasks.complete(task);\r
-                            } catch (DatabaseException e) {\r
-                                result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
-                            }\r
-                        }\r
-                    }\r
-                });\r
-                return result;\r
-            } catch (DatabaseException e) {\r
-                return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Resource label provider failed unexpectedly.", e);\r
-            } finally {\r
-                labelProvider.asyncFireChangedEvent(new LabelProviderChangedEvent(labelProvider));\r
-            }\r
-        }\r
-\r
-        protected void runTask(ResourceManager rm, ReadGraph graph, Task task) throws DatabaseException {\r
-            task.label = getText(graph, task.resource);\r
-            task.image = getImage(rm, graph, task.resource);\r
-        }\r
-\r
-        protected String getText(ReadGraph graph, Resource resource) {\r
-            try {\r
-                return graph.adapt(resource, String.class);\r
-            } catch (DatabaseException e) {\r
-                try {\r
-                    return NameUtils.getSafeName(graph, resource);\r
-                } catch (DatabaseException e2) {\r
-                    return "";\r
-                }\r
-            }\r
-        }\r
-\r
-        public Image getImage(ResourceManager rm, ReadGraph graph, Resource resource) throws DatabaseException {\r
-            ImageDescriptor i = getImageDescriptor(graph, resource);\r
-            return i == null ? null : (Image) rm.get(i);\r
-        }\r
-\r
-        public ImageDescriptor getImageDescriptor(ReadGraph graph, Resource resource, ImageDescriptor... decorations)\r
-                throws DatabaseException {\r
-            ImageDescriptor baseImage = ImageUtil.adaptImageDescriptor(graph, resource);\r
-            if (baseImage == null)\r
-                return null;\r
-            if (decorations == null || decorations.length == 0)\r
-                return baseImage;\r
-            List<ImageDescriptor> images = new ArrayList<ImageDescriptor>(1+decorations.length);\r
-            images.add(baseImage);\r
-            for (ImageDescriptor image : decorations)\r
-                images.add(image);\r
-            return CompositionImageDescriptor.compose(images);\r
-        }\r
-\r
-    }\r
-\r
-    private static class TaskRepository {\r
-        Map<Resource, Task> tasks = new THashMap<Resource, Task>();\r
-        Map<Resource, Task> completed = new THashMap<Resource, Task>();\r
-\r
-        ResourceManager resourceManager;\r
-\r
-        public TaskRepository(Device device) {\r
-            this.resourceManager = new DeviceResourceManager(device);\r
-        }\r
-\r
-        public boolean isDisposed() {\r
-            return resourceManager == null;\r
-        }\r
-\r
-        public void dispose() {\r
-            if (resourceManager != null) {\r
-                resourceManager.dispose();\r
-                resourceManager = null;\r
-            }\r
-        }\r
-\r
-        public Task queue(Task task) {\r
-            synchronized (tasks) {\r
-                tasks.put(task.resource, task);\r
-            }\r
-            return task;\r
-        }\r
-\r
-        public Task queue(Resource resource) {\r
-            return queue(new Task(resource));\r
-        }\r
-\r
-        public void clear() {\r
-            pruneTasks();\r
-            pruneCompleted();\r
-        }\r
-\r
-        public Task[] pruneTasks() {\r
-            synchronized (tasks) {\r
-                Task[] result = tasks.values().toArray(Task.NONE);\r
-                tasks.clear();\r
-                return result;\r
-            }\r
-        }\r
-\r
-        public void pruneCompleted() {\r
-            synchronized (completed) {\r
-                completed.clear();\r
-            }\r
-        }\r
-\r
-        public void complete(Task task) {\r
-            synchronized (completed) {\r
-                 completed.put(task.resource, task);\r
-            }\r
-        }\r
-\r
-        public Task getCompleted(Resource r) {\r
-            synchronized (completed) {\r
-                return completed.get(r);\r
-            }\r
-        }\r
-\r
-    }\r
-\r
-    private static class Task {\r
-        public static final Task[] NONE = {};\r
-\r
-        public final Resource resource;\r
-        public String label = "";\r
-        public Image image;\r
-\r
-        public Task(Resource resource) {\r
-            this.resource = resource;\r
-        }\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2013 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * 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:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *     Semantum Oy - index based searching (#4255)
+ *******************************************************************************/
+package org.simantics.ui.workbench.dialogs;
+
+import gnu.trove.map.hash.THashMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.resource.DeviceResourceManager;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.progress.IProgressConstants;
+import org.simantics.DatabaseJob;
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.ui.icons.ImageDescriptorProvider;
+import org.simantics.ui.icons.ImageUtil;
+import org.simantics.ui.internal.Activator;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.ui.gfx.CompositionImageDescriptor;
+
+/**
+ * ResourceLabelProvider is a label provider for Simantics database
+ * {@link Resource}s.
+ * 
+ * <p>
+ * This label provider utilizes {@link ImageDescriptorProvider} adapters for
+ * determining the icon to use.
+ * 
+ * @author Toni Kalajainen
+ * @author Tuukka Lehtonen
+ * 
+ * @see Resource
+ * @see ILabelProvider
+ * @see ILabelProviderListener
+ */
+public class ResourceLabelProvider implements ILabelProvider {
+
+    protected List<ILabelProviderListener> listeners = null;
+    protected IThreadWorkQueue thread;
+    protected TaskRepository tasks;
+    protected LoadJob loadJob;
+
+    public ResourceLabelProvider(Display display) {
+        this.thread = SWTThread.getThreadAccess(display);
+        this.tasks = new TaskRepository(display);
+        this.loadJob = new LoadJob("Resource Labeler", tasks, this);
+    }
+
+    public void fireChangedEvent(LabelProviderChangedEvent e) {
+        ILabelProviderListener[] listeners;
+        synchronized(this) {
+            if (this.listeners == null)
+                return;
+            listeners = this.listeners.toArray(new ILabelProviderListener[0]);
+        }
+        for (ILabelProviderListener l : listeners)
+            l.labelProviderChanged(e);
+    }
+
+    public void asyncFireChangedEvent(final LabelProviderChangedEvent e) {
+        thread.asyncExec(new Runnable() {
+            @Override
+            public void run() {
+                fireChangedEvent(e);
+            }
+        });
+    }
+
+    public synchronized void addListener(ILabelProviderListener listener) {
+        if (listeners == null)
+            listeners = new CopyOnWriteArrayList<ILabelProviderListener>();
+        listeners.add(listener);
+    }
+
+    public void dispose() {
+        if (listeners != null)
+            listeners.clear();
+        loadJob.dispose();
+        tasks.dispose();
+    }
+
+    public void clear() {
+        tasks.clear();
+    }
+
+    public boolean isLabelProperty(Object element, String property) {
+        System.out.println("isLabelProperty(" + element + ", " + property + ")");
+        return false;
+    }
+
+    public synchronized void removeListener(ILabelProviderListener listener) {
+        if (listeners != null)
+            listeners.remove(listener);
+    }
+
+    @Override
+    public Image getImage(Object element) {
+        if (element == null)
+            return null;
+        Resource r = (Resource) element;
+        Task task = tasks.getCompleted(r);
+        if (task != null) {
+            return task.image;
+        }
+        task = tasks.queue(r);
+        loadJob.scheduleIfNecessary(100);
+        return task.image;
+    }
+
+    @Override
+    public String getText(Object element) {
+        if (element == null)
+            return null;
+        Resource r = (Resource) element;
+        Task task = tasks.getCompleted(r);
+        if (task != null) {
+            return task.label;
+        }
+        task = tasks.queue(r);
+        loadJob.scheduleIfNecessary(100);
+        return task.label;
+    }
+
+    private static class LoadJob extends DatabaseJob {
+        private TaskRepository tasks;
+        private ResourceLabelProvider labelProvider;
+        private AtomicBoolean isScheduled = new AtomicBoolean();
+
+        public LoadJob(String name, TaskRepository tasks, ResourceLabelProvider labelProvider) {
+            super(name);
+            setUser(false);
+            setSystem(true);
+            setPriority(SHORT);
+            setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
+            this.tasks = tasks;
+            this.labelProvider = labelProvider;
+        }
+
+        /**
+         * Schedules the job with {@link Job#schedule()} if necessary, i.e. if
+         * not already scheduled. This method avoids some unnecessary overhead
+         * caused by repeatedly invoking {@link Job#schedule()}.
+         * 
+         * @return <code>true</code> if scheduled, <code>false</code> otherwise
+         */
+        public boolean scheduleIfNecessary(long delay) {
+            if (isScheduled.compareAndSet(false, true)) {
+                schedule(delay);
+                return true;
+            }
+            return false;
+        }
+
+        void dispose() {
+            tasks = null;
+            labelProvider = null;
+        }
+
+        @Override
+        public boolean shouldSchedule() {
+            return tasks != null;
+        }
+
+        @Override
+        public boolean shouldRun() {
+            return tasks != null;
+        }
+
+        @Override
+        protected IStatus run(IProgressMonitor monitor) {
+            try {
+                isScheduled.set(false);
+
+                TaskRepository tr = tasks;
+                if (tr == null)
+                    return Status.CANCEL_STATUS;
+
+                Task[] taskList = tr.pruneTasks();
+                return runTasks(tr, taskList);
+            } finally {
+                monitor.done();
+            }
+        }
+
+        private IStatus runTasks(final TaskRepository tr, final Task[] taskList) {
+            Session session = Simantics.peekSession();
+            if (session == null)
+                return Status.CANCEL_STATUS;
+            final ResourceManager rm = tr.resourceManager;
+            if (rm == null)
+                return Status.CANCEL_STATUS;
+
+            final MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems encountered while invoking resource label provider:", null);
+            try {
+                Simantics.getSession().syncRequest(new ReadRequest() {
+                    @Override
+                    public void run(ReadGraph graph) throws DatabaseException {
+                        for (Task task : taskList) {
+                            try {
+                                if (tr.isDisposed())
+                                    return;
+
+                                runTask(rm, graph, task);
+                                tasks.complete(task);
+                            } catch (DatabaseException e) {
+                                result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
+                            }
+                        }
+                    }
+                });
+                return result;
+            } catch (DatabaseException e) {
+                return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Resource label provider failed unexpectedly.", e);
+            } finally {
+                labelProvider.asyncFireChangedEvent(new LabelProviderChangedEvent(labelProvider));
+            }
+        }
+
+        protected void runTask(ResourceManager rm, ReadGraph graph, Task task) throws DatabaseException {
+            task.label = getText(graph, task.resource);
+            task.image = getImage(rm, graph, task.resource);
+        }
+
+        protected String getText(ReadGraph graph, Resource resource) {
+            try {
+                return graph.adapt(resource, String.class);
+            } catch (DatabaseException e) {
+                try {
+                    return NameUtils.getSafeName(graph, resource);
+                } catch (DatabaseException e2) {
+                    return "";
+                }
+            }
+        }
+
+        public Image getImage(ResourceManager rm, ReadGraph graph, Resource resource) throws DatabaseException {
+            ImageDescriptor i = getImageDescriptor(graph, resource);
+            return i == null ? null : (Image) rm.get(i);
+        }
+
+        public ImageDescriptor getImageDescriptor(ReadGraph graph, Resource resource, ImageDescriptor... decorations)
+                throws DatabaseException {
+            ImageDescriptor baseImage = ImageUtil.adaptImageDescriptor(graph, resource);
+            if (baseImage == null)
+                return null;
+            if (decorations == null || decorations.length == 0)
+                return baseImage;
+            List<ImageDescriptor> images = new ArrayList<ImageDescriptor>(1+decorations.length);
+            images.add(baseImage);
+            for (ImageDescriptor image : decorations)
+                images.add(image);
+            return CompositionImageDescriptor.compose(images);
+        }
+
+    }
+
+    private static class TaskRepository {
+        Map<Resource, Task> tasks = new THashMap<Resource, Task>();
+        Map<Resource, Task> completed = new THashMap<Resource, Task>();
+
+        ResourceManager resourceManager;
+
+        public TaskRepository(Device device) {
+            this.resourceManager = new DeviceResourceManager(device);
+        }
+
+        public boolean isDisposed() {
+            return resourceManager == null;
+        }
+
+        public void dispose() {
+            if (resourceManager != null) {
+                resourceManager.dispose();
+                resourceManager = null;
+            }
+        }
+
+        public Task queue(Task task) {
+            synchronized (tasks) {
+                tasks.put(task.resource, task);
+            }
+            return task;
+        }
+
+        public Task queue(Resource resource) {
+            return queue(new Task(resource));
+        }
+
+        public void clear() {
+            pruneTasks();
+            pruneCompleted();
+        }
+
+        public Task[] pruneTasks() {
+            synchronized (tasks) {
+                Task[] result = tasks.values().toArray(Task.NONE);
+                tasks.clear();
+                return result;
+            }
+        }
+
+        public void pruneCompleted() {
+            synchronized (completed) {
+                completed.clear();
+            }
+        }
+
+        public void complete(Task task) {
+            synchronized (completed) {
+                 completed.put(task.resource, task);
+            }
+        }
+
+        public Task getCompleted(Resource r) {
+            synchronized (completed) {
+                return completed.get(r);
+            }
+        }
+
+    }
+
+    private static class Task {
+        public static final Task[] NONE = {};
+
+        public final Resource resource;
+        public String label = "";
+        public Image image;
+
+        public Task(Resource resource) {
+            this.resource = resource;
+        }
+    }
+
+}