]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.issues.common/src/org/simantics/issues/common/IssueUtils.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.issues.common / src / org / simantics / issues / common / IssueUtils.java
index f1dcadc892b95cab535904f865a5c117f2e8cfdc..866323bab9d1132d16d86eb529901e789ed3495c 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 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
- *******************************************************************************/\r
-package org.simantics.issues.common;\r
-\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.text.DateFormat;\r
-import java.text.SimpleDateFormat;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-import java.util.concurrent.atomic.AtomicBoolean;\r
-\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.util.URIStringUtils;\r
-import org.simantics.db.Disposable;\r
-import org.simantics.db.Issue;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.RequestProcessor;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.VirtualGraph;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.primitiverequest.Objects;\r
-import org.simantics.db.common.procedure.adapter.DisposableSyncListener;\r
-import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
-import org.simantics.db.common.procedure.single.SingleSetSyncListener;\r
-import org.simantics.db.common.request.ResourceRead3;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.ListUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.request.ActiveModels;\r
-import org.simantics.db.layer0.request.Model;\r
-import org.simantics.db.layer0.request.PossibleModel;\r
-import org.simantics.db.layer0.util.RemoverUtil;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.service.VirtualGraphSupport;\r
-import org.simantics.issues.Severity;\r
-import org.simantics.issues.ontology.IssueResource;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.operation.Layer0X;\r
-import org.simantics.scl.runtime.function.FunctionImpl2;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class IssueUtils {\r
-    private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);\r
-\r
-    public static Resource toSeverityResource(IssueResource ISSUE, Severity severity) {\r
-        switch (severity) {\r
-            case ERROR: return ISSUE.Severity_Error;\r
-            case FATAL: return ISSUE.Severity_Fatal;\r
-            case INFO: return ISSUE.Severity_Info;\r
-            case WARNING: return ISSUE.Severity_Warning;\r
-            case NOTE: return ISSUE.Severity_Note;\r
-            default: return null;\r
-        }\r
-    }\r
-\r
-    public static Severity toSeverity(IssueResource ISSUE, Resource severity) {\r
-        if (severity == null)\r
-            return null;\r
-        if (severity.equals(ISSUE.Severity_Fatal))\r
-            return Severity.FATAL;\r
-        if (severity.equals(ISSUE.Severity_Error))\r
-            return Severity.ERROR;\r
-        if (severity.equals(ISSUE.Severity_Info))\r
-            return Severity.INFO;\r
-        if (severity.equals(ISSUE.Severity_Warning))\r
-            return Severity.WARNING;\r
-        if (severity.equals(ISSUE.Severity_Note))\r
-            return Severity.NOTE;\r
-        return null;\r
-    }\r
-\r
-    private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {\r
-\r
-        private final IssueSource is;\r
-\r
-        public IssueSourceDirtyListener(IssueSource is) {\r
-            this.is = is;\r
-        }\r
-\r
-        @Override\r
-        public Boolean apply(ReadGraph graph, final List<Resource> resources) {\r
-            VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
-            VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
-            if (graph instanceof WriteGraph) {\r
-                try {\r
-                    if(is.needUpdate(graph, resources)) {\r
-                        graph.sync(new WriteRequest(vg) {\r
-                            @Override\r
-                            public void perform(WriteGraph graph) throws DatabaseException {\r
-                                is.update(graph, resources);\r
-                            }\r
-                        });\r
-                    }\r
-                } catch (DatabaseException e) {\r
-                    LOGGER.error("Updating issue source failed.", e);\r
-                }\r
-            } else {\r
-                Session session = Simantics.getSession();\r
-                session.asyncRequest(new WriteRequest(vg) {\r
-                    @Override\r
-                    public void perform(WriteGraph graph) throws DatabaseException {\r
-                        is.update(graph, resources);\r
-                    }\r
-                });\r
-            }\r
-\r
-            return true;\r
-        }\r
-\r
-    }\r
-\r
-    private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {\r
-\r
-        private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();\r
-        private final AtomicBoolean disposed;\r
-        private final Resource source;\r
-        private final Resource model;\r
-\r
-        public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {\r
-            this.disposed = disposed;\r
-            this.source = source;\r
-            this.model = model;\r
-        }\r
-\r
-        class IssueValidityListener extends DisposableSyncListener<Boolean> {\r
-\r
-            final private Resource issue;\r
-\r
-            public IssueValidityListener(Resource issue) {\r
-                this.issue = issue;\r
-            }\r
-\r
-            @Override\r
-            public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {\r
-                if(!valid) {\r
-                    VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
-                    VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
-                    graph.asyncRequest(new WriteRequest(vg) {\r
-\r
-                        @Override\r
-                        public void perform(WriteGraph graph) throws DatabaseException {\r
-                            Issue desc = graph.sync(new StandardIssueDescription(issue));\r
-                            if(desc == null)\r
-                                return;\r
-                            Resource context = (Resource)desc.getMainContext();\r
-                            new DependencyIssueSynchronizer2(context, source).perform(graph);\r
-                        }\r
-                        \r
-                    });\r
-                }\r
-            }\r
-\r
-            @Override\r
-            public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {\r
-                LOGGER.error("IssueValidityListener received an exception.", throwable);\r
-            }\r
-\r
-        }\r
-\r
-        @Override\r
-        public void add(ReadGraph graph, final Resource issue) throws DatabaseException {\r
-            IssueValidityListener listener = new IssueValidityListener(issue);\r
-\r
-            graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {\r
-\r
-                @Override\r
-                public Boolean perform(ReadGraph graph) throws DatabaseException {\r
-                    Issue desc = graph.sync(new StandardIssueDescription(resource));\r
-                    if(desc == null)\r
-                        return false;\r
-                    Resource context = (Resource)desc.getMainContext();\r
-                    return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());\r
-                }\r
-\r
-            }, listener);\r
-\r
-            listeners.put(issue, listener);\r
-        }\r
-\r
-        @Override\r
-        public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {\r
-            IssueValidityListener listener = listeners.remove(issue);\r
-            if(listener != null)\r
-                listener.dispose();\r
-        }\r
-\r
-        @Override\r
-        public void exception(ReadGraph graph, Throwable t) {\r
-            LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);\r
-        }\r
-\r
-        @Override\r
-        public boolean isDisposed() {\r
-            boolean disp = disposed.get();\r
-            if (disp) {\r
-                // Ensure validity listeners are cleared eventually.\r
-                if (!listeners.isEmpty()) {\r
-                    for (IssueValidityListener listener : listeners.values()) {\r
-                        listener.dispose();\r
-                    }\r
-                    listeners.clear();\r
-                }\r
-            }\r
-            return disp;\r
-        }\r
-\r
-    }\r
-\r
-    private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {\r
-\r
-        private final AtomicBoolean disposed;\r
-        private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();\r
-\r
-        public ActiveIssueSourceListener(AtomicBoolean disposed) {\r
-            this.disposed = disposed;\r
-        }\r
-\r
-        @Override\r
-        public void add(ReadGraph graph, final Resource source) throws DatabaseException {\r
-            IssueResource ISSUE = IssueResource.getInstance(graph);\r
-            boolean isListeningTracker = graph.isInstanceOf(source, ISSUE.Sources_ListeningDependencyTracker);\r
-            IssueSource is = graph.adapt(source, IssueSource.class);\r
-            final Resource model = isListeningTracker ? graph.syncRequest(new Model(source)) : null;\r
-\r
-            IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);\r
-            is.addDirtyListener(listener);\r
-            sources.put(source, Pair.make(is, listener));\r
-\r
-            if (isListeningTracker) {\r
-                graph.asyncRequest(\r
-                        new Objects(source, ISSUE.IssueSource_Manages),\r
-                        new IssueSourceManagedIssuesListener(disposed, source, model));\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void remove(ReadGraph graph, final Resource source) throws DatabaseException {\r
-            Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);\r
-            if (is != null)\r
-                is.first.removeDirtyListener(is.second);\r
-        }\r
-\r
-        @Override\r
-        public void exception(ReadGraph graph, Throwable t) {\r
-            LOGGER.error("ActiveIssueSourceListener received an exception.", t);\r
-        }\r
-\r
-        @Override\r
-        public boolean isDisposed() {\r
-            return disposed.get();\r
-        }\r
-\r
-    }\r
-\r
-    public static Disposable listenActiveProjectIssueSources(RequestProcessor processor, Resource project) throws DatabaseException {\r
-        final AtomicBoolean disposed = new AtomicBoolean(false);\r
-        processor.syncRequest(\r
-                new ActiveProjectIssueSources(project),\r
-                new ActiveIssueSourceListener(disposed));\r
-        return new Disposable() {\r
-            @Override\r
-            public void dispose() {\r
-                disposed.set(true);\r
-            }\r
-        };\r
-    }\r
-\r
-    public static List<Resource> getContextsForProperty(ReadGraph graph, Variable property) throws DatabaseException {\r
-       IssueResource ISSUE = IssueResource.getInstance(graph);\r
-       Variable issueVariable = property.getParent(graph);\r
-       Resource issueResource = issueVariable.getRepresents(graph);\r
-       Resource list = graph.getPossibleObject(issueResource, ISSUE.Issue_HasContexts);\r
-       if(list != null)\r
-               return ListUtils.toList(graph, list);\r
-       else\r
-               return Collections.emptyList();\r
-    }\r
-    \r
-       public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {\r
-\r
-               if(contexts.isEmpty()) return;\r
-               \r
-               IssueResource IR = IssueResource.getInstance(graph);\r
-\r
-               // The main context\r
-       graph.claim(issue, IR.Issue_HasContext, contexts.get(0));\r
-       // A possible parent\r
-               Layer0 L0 = Layer0.getInstance(graph);\r
-               Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);\r
-        if(parent != null) {\r
-               graph.claim(issue, IR.Issue_HasContext, parent);\r
-        }\r
-               \r
-       }    \r
-    public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
-       \r
-               Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
-               if(model == null) throw new DatabaseException("No model for main context");\r
-               \r
-               newUserIssueForModel(graph, model, label, severity, contexts);\r
-               \r
-    }\r
-    \r
-    public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {\r
-       \r
-       Resource project = Simantics.getProjectResource();\r
-       Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));\r
-        if (activeModels.size() != 1)\r
-            return null;\r
-\r
-        IssueResource ISSUE = IssueResource.getInstance(graph);\r
-        Resource issue = null;\r
-        for (Resource model : activeModels) {\r
-            issue = newUserIssueForModel(graph, model, "New User Issue", ISSUE.Severity_Note, Collections.<Resource>emptyList());\r
-        }\r
-               return issue;\r
-        \r
-    }\r
-     \r
-\r
-    public static Resource newUserIssueForModel(WriteGraph graph, Resource model, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
-        IssueResource ISSUE = IssueResource.getInstance(graph);\r
-       return newUserIssueForModel(graph, model, ISSUE.Issue, label, severity, contexts);\r
-    }\r
-\r
-    public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {\r
-\r
-        Layer0 L0 = Layer0.getInstance(graph);\r
-        IssueResource ISSUE = IssueResource.getInstance(graph);\r
-       \r
-       Resource issue = graph.newResource();\r
-               graph.claim(issue, ISSUE.UserIssue, ISSUE.UserIssue, issue);\r
-               graph.claim(issue, L0.InstanceOf, null, type);\r
-               graph.claim(issue, ISSUE.Issue_HasSeverity, null, severity);\r
-               graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
-               writeAdditionalContext(graph, issue, contexts);\r
-               graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
-               graph.claimLiteral(issue, L0.HasLabel, label, Bindings.STRING);\r
-               DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
-               Object time = format.format(new Date());\r
-               graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
-               graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
-               return issue;\r
-       \r
-    }\r
-    \r
-    /**\r
-     * Creates a new issue based on SimpleIssue structure.\r
-     */\r
-    public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {\r
-\r
-        Layer0 L0 = Layer0.getInstance(graph);\r
-        IssueResource ISSUE = IssueResource.getInstance(graph);\r
-        \r
-        Resource issue = graph.newResource();\r
-        graph.claim(issue, L0.InstanceOf, null, issueType);\r
-        graph.claim(issue, ISSUE.Issue_HasSeverity, null, toSeverityResource(ISSUE, simpleIssue.severity));\r
-        graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));\r
-        writeAdditionalContext(graph, issue, contexts);\r
-        graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);\r
-        graph.claimLiteral(issue, L0.HasLabel, simpleIssue.label, Bindings.STRING);\r
-        DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");\r
-        Object time = format.format(new Date());\r
-        graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);\r
-        graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);\r
-        \r
-    }\r
-    \r
-    /**\r
-     * Returns the set of all issues with given type.\r
-     */\r
-    public static Set<SimpleIssue> getSimpleIssues(ReadGraph graph, List<Resource> contexts, Resource issueType)\r
-            throws DatabaseException {\r
-        Layer0 L0 = Layer0.getInstance(graph);\r
-        IssueResource ISSUE = IssueResource.getInstance(graph);\r
-        \r
-        THashSet<SimpleIssue> currentIssues = null;\r
-        for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {\r
-            if(!graph.isInstanceOf(issue, issueType))\r
-                continue;\r
-            List<Resource> curContexts = \r
-                    ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));\r
-            if(!contexts.equals(curContexts))\r
-                continue;\r
-            if(currentIssues == null)\r
-                currentIssues = new THashSet<SimpleIssue>();\r
-            currentIssues.add(new SimpleIssue(\r
-                    (String)graph.getRelatedValue(issue, L0.HasLabel),\r
-                    toSeverity(ISSUE, graph.getSingleObject(issue, ISSUE.Issue_HasSeverity)), \r
-                    issue));\r
-        }\r
-        if(currentIssues == null)\r
-            return Collections.emptySet();\r
-        else\r
-            return currentIssues;\r
-    }\r
-    \r
-    /**\r
-     * Creates and removes issues so that after the operation, \r
-     * the context has exactly the given issues with the given type.\r
-     */\r
-    public static void setSimpleIssues(WriteGraph graph, Resource model, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
-        Set<SimpleIssue> currentIssues = getSimpleIssues(graph, contexts, issueType);\r
-        for(SimpleIssue newIssue : issues) {\r
-            if(currentIssues.contains(newIssue))\r
-                currentIssues.remove(newIssue);\r
-            else\r
-                newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);\r
-        }\r
-        for(SimpleIssue oldIssue : currentIssues)\r
-            RemoverUtil.remove(graph, oldIssue.issueResource);\r
-    }\r
-    \r
-    /**\r
-     * Creates and removes issues so that after the operation, \r
-     * the context has exactly the given issues with the given type.\r
-     */\r
-    public static void setSimpleIssues(WriteGraph graph, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {\r
-        Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
-        if(model == null) throw new DatabaseException("No model for main context");\r
-     \r
-        setSimpleIssues(graph, model, contexts, issueType, issues);\r
-    }\r
-    \r
-    /**\r
-     * Creates and removes issues so that after the operation, \r
-     * the context has exactly the given issues with the given type.\r
-     * Because this method is called in read transaction, it \r
-     * makes a write transaction if necessary and continues the\r
-     * operation there.\r
-     */\r
-    public static void setSimpleIssuesAsync(ReadGraph graph,\r
-            final List<Resource> contexts,\r
-            final Resource issueType, \r
-            final SimpleIssue ... issues) throws DatabaseException {\r
-        Resource model = graph.sync(new PossibleModel(contexts.get(0)));\r
-        if(model == null) throw new DatabaseException("No model for main context");\r
-     \r
-        setSimpleIssuesAsync(graph, model, contexts, issueType, issues);\r
-    }\r
-    \r
-    /**\r
-     * Creates and removes issues so that after the operation, \r
-     * the context has exactly the given issues with the given type.\r
-     * Because this method is called in read transaction, it \r
-     * makes a write transaction if necessary and continues the\r
-     * operation there.\r
-     */\r
-    public static void setSimpleIssuesAsync(ReadGraph graph, \r
-            final Resource model, \r
-            final List<Resource> contexts,\r
-            final Resource issueType, \r
-            final SimpleIssue ... issues) throws DatabaseException {\r
-        Set<SimpleIssue> oldIssues = getSimpleIssues(graph, contexts, issueType);\r
-        \r
-        boolean needsUpdating = false;\r
-        if(issues.length != oldIssues.size())\r
-            needsUpdating = true;\r
-        else \r
-            for(SimpleIssue newIssue : issues) {\r
-                if(!oldIssues.contains(newIssue)) {\r
-                    needsUpdating = true;\r
-                    break;\r
-                }\r
-            }\r
-        if(needsUpdating) {\r
-            VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);\r
-            VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);\r
-            Simantics.getSession().asyncRequest(new WriteRequest(vg) {\r
-                \r
-                @Override\r
-                public void perform(WriteGraph graph) throws DatabaseException {\r
-                    setSimpleIssues(graph, model, contexts, issueType, issues);\r
-                }\r
-            });\r
-        }\r
-    }\r
-    \r
-       public static Resource addIssueSource(WriteGraph g, Resource model, Resource sourceType, String name) throws DatabaseException {\r
-        Layer0 L0 = Layer0.getInstance(g);\r
-        Layer0X L0X = Layer0X.getInstance(g);\r
-\r
-        Resource source = g.newResource();\r
-        g.claim(source, L0.InstanceOf, null, sourceType);\r
-        g.addLiteral(source, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);\r
-        g.claim(source, L0X.IsActivatedBy, L0X.Activates, model);\r
-        g.claim(source, L0.PartOf, L0.ConsistsOf, model);\r
-        return source;\r
-    }\r
-\r
-    public static String pathString(String uri, int startIndex) {\r
-        StringBuilder sb = new StringBuilder(uri.length() - startIndex + 1);\r
-        sb.append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
-        while (true) {\r
-            int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);\r
-            if (nextSlash == -1) {\r
-                sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));\r
-                break;\r
-            }\r
-            sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);\r
-            startIndex = nextSlash + 1;\r
-        }\r
-        return sb.toString();\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 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
+ *******************************************************************************/
+package org.simantics.issues.common;
+
+import gnu.trove.set.hash.THashSet;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.util.URIStringUtils;
+import org.simantics.db.Disposable;
+import org.simantics.db.Issue;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.VirtualGraph;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.primitiverequest.Objects;
+import org.simantics.db.common.procedure.adapter.DisposableSyncListener;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
+import org.simantics.db.common.procedure.single.SingleSetSyncListener;
+import org.simantics.db.common.request.ResourceRead3;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.ListUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.ActiveModels;
+import org.simantics.db.layer0.request.Model;
+import org.simantics.db.layer0.request.PossibleModel;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.service.VirtualGraphSupport;
+import org.simantics.issues.Severity;
+import org.simantics.issues.ontology.IssueResource;
+import org.simantics.layer0.Layer0;
+import org.simantics.operation.Layer0X;
+import org.simantics.scl.runtime.function.FunctionImpl2;
+import org.simantics.utils.datastructures.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class IssueUtils {
+    private static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
+
+    public static Resource toSeverityResource(IssueResource ISSUE, Severity severity) {
+        switch (severity) {
+            case ERROR: return ISSUE.Severity_Error;
+            case FATAL: return ISSUE.Severity_Fatal;
+            case INFO: return ISSUE.Severity_Info;
+            case WARNING: return ISSUE.Severity_Warning;
+            case NOTE: return ISSUE.Severity_Note;
+            default: return null;
+        }
+    }
+
+    public static Severity toSeverity(IssueResource ISSUE, Resource severity) {
+        if (severity == null)
+            return null;
+        if (severity.equals(ISSUE.Severity_Fatal))
+            return Severity.FATAL;
+        if (severity.equals(ISSUE.Severity_Error))
+            return Severity.ERROR;
+        if (severity.equals(ISSUE.Severity_Info))
+            return Severity.INFO;
+        if (severity.equals(ISSUE.Severity_Warning))
+            return Severity.WARNING;
+        if (severity.equals(ISSUE.Severity_Note))
+            return Severity.NOTE;
+        return null;
+    }
+
+    private static class IssueSourceDirtyListener extends FunctionImpl2<ReadGraph, List<Resource>, Boolean> {
+
+        private final IssueSource is;
+
+        public IssueSourceDirtyListener(IssueSource is) {
+            this.is = is;
+        }
+
+        @Override
+        public Boolean apply(ReadGraph graph, final List<Resource> resources) {
+            VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+            VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+            if (graph instanceof WriteGraph) {
+                try {
+                    if(is.needUpdate(graph, resources)) {
+                        graph.sync(new WriteRequest(vg) {
+                            @Override
+                            public void perform(WriteGraph graph) throws DatabaseException {
+                                is.update(graph, resources);
+                            }
+                        });
+                    }
+                } catch (DatabaseException e) {
+                    LOGGER.error("Updating issue source failed.", e);
+                }
+            } else {
+                Session session = Simantics.getSession();
+                session.asyncRequest(new WriteRequest(vg) {
+                    @Override
+                    public void perform(WriteGraph graph) throws DatabaseException {
+                        is.update(graph, resources);
+                    }
+                });
+            }
+
+            return true;
+        }
+
+    }
+
+    private static class IssueSourceManagedIssuesListener extends SingleSetSyncListener<Resource> {
+
+        private final HashMap<Resource, IssueValidityListener> listeners = new HashMap<>();
+        private final AtomicBoolean disposed;
+        private final Resource source;
+        private final Resource model;
+
+        public IssueSourceManagedIssuesListener(AtomicBoolean disposed, Resource source, Resource model) {
+            this.disposed = disposed;
+            this.source = source;
+            this.model = model;
+        }
+
+        class IssueValidityListener extends DisposableSyncListener<Boolean> {
+
+            final private Resource issue;
+
+            public IssueValidityListener(Resource issue) {
+                this.issue = issue;
+            }
+
+            @Override
+            public void execute(ReadGraph graph, Boolean valid) throws DatabaseException {
+                if(!valid) {
+                    VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+                    VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+                    graph.asyncRequest(new WriteRequest(vg) {
+
+                        @Override
+                        public void perform(WriteGraph graph) throws DatabaseException {
+                            Issue desc = graph.sync(new StandardIssueDescription(issue));
+                            if(desc == null)
+                                return;
+                            Resource context = (Resource)desc.getMainContext();
+                            new DependencyIssueSynchronizer2(context, source).perform(graph);
+                        }
+                        
+                    });
+                }
+            }
+
+            @Override
+            public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
+                LOGGER.error("IssueValidityListener received an exception.", throwable);
+            }
+
+        }
+
+        @Override
+        public void add(ReadGraph graph, final Resource issue) throws DatabaseException {
+            IssueValidityListener listener = new IssueValidityListener(issue);
+
+            graph.asyncRequest(new ResourceRead3<Boolean>(issue, model, source) {
+
+                @Override
+                public Boolean perform(ReadGraph graph) throws DatabaseException {
+                    Issue desc = graph.sync(new StandardIssueDescription(resource));
+                    if(desc == null)
+                        return false;
+                    Resource context = (Resource)desc.getMainContext();
+                    return graph.syncRequest(new DependencyIssueValidator2(context, resource2, resource3), TransientCacheListener.<Boolean>instance());
+                }
+
+            }, listener);
+
+            listeners.put(issue, listener);
+        }
+
+        @Override
+        public void remove(ReadGraph graph, final Resource issue) throws DatabaseException {
+            IssueValidityListener listener = listeners.remove(issue);
+            if(listener != null)
+                listener.dispose();
+        }
+
+        @Override
+        public void exception(ReadGraph graph, Throwable t) {
+            LOGGER.error("IssueSourceManagedIssuesListener received an exception.", t);
+        }
+
+        @Override
+        public boolean isDisposed() {
+            boolean disp = disposed.get();
+            if (disp) {
+                // Ensure validity listeners are cleared eventually.
+                if (!listeners.isEmpty()) {
+                    for (IssueValidityListener listener : listeners.values()) {
+                        listener.dispose();
+                    }
+                    listeners.clear();
+                }
+            }
+            return disp;
+        }
+
+    }
+
+    private static class ActiveIssueSourceListener extends SingleSetSyncListener<Resource> {
+
+        private final AtomicBoolean disposed;
+        private Map<Resource, Pair<IssueSource, IssueSourceDirtyListener>> sources = new HashMap<>();
+
+        public ActiveIssueSourceListener(AtomicBoolean disposed) {
+            this.disposed = disposed;
+        }
+
+        @Override
+        public void add(ReadGraph graph, final Resource source) throws DatabaseException {
+            IssueResource ISSUE = IssueResource.getInstance(graph);
+            boolean isListeningTracker = graph.isInstanceOf(source, ISSUE.Sources_ListeningDependencyTracker);
+            IssueSource is = graph.adapt(source, IssueSource.class);
+            final Resource model = isListeningTracker ? graph.syncRequest(new Model(source)) : null;
+
+            IssueSourceDirtyListener listener = new IssueSourceDirtyListener(is);
+            is.addDirtyListener(listener);
+            sources.put(source, Pair.make(is, listener));
+
+            if (isListeningTracker) {
+                graph.asyncRequest(
+                        new Objects(source, ISSUE.IssueSource_Manages),
+                        new IssueSourceManagedIssuesListener(disposed, source, model));
+            }
+        }
+
+        @Override
+        public void remove(ReadGraph graph, final Resource source) throws DatabaseException {
+            Pair<IssueSource, IssueSourceDirtyListener> is = sources.remove(source);
+            if (is != null)
+                is.first.removeDirtyListener(is.second);
+        }
+
+        @Override
+        public void exception(ReadGraph graph, Throwable t) {
+            LOGGER.error("ActiveIssueSourceListener received an exception.", t);
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return disposed.get();
+        }
+
+    }
+
+    public static Disposable listenActiveProjectIssueSources(RequestProcessor processor, Resource project) throws DatabaseException {
+        final AtomicBoolean disposed = new AtomicBoolean(false);
+        processor.syncRequest(
+                new ActiveProjectIssueSources(project),
+                new ActiveIssueSourceListener(disposed));
+        return new Disposable() {
+            @Override
+            public void dispose() {
+                disposed.set(true);
+            }
+        };
+    }
+
+    public static List<Resource> getContextsForProperty(ReadGraph graph, Variable property) throws DatabaseException {
+       IssueResource ISSUE = IssueResource.getInstance(graph);
+       Variable issueVariable = property.getParent(graph);
+       Resource issueResource = issueVariable.getRepresents(graph);
+       Resource list = graph.getPossibleObject(issueResource, ISSUE.Issue_HasContexts);
+       if(list != null)
+               return ListUtils.toList(graph, list);
+       else
+               return Collections.emptyList();
+    }
+    
+       public static void writeAdditionalContext(WriteGraph graph, Resource issue, List<Resource> contexts) throws DatabaseException {
+
+               if(contexts.isEmpty()) return;
+               
+               IssueResource IR = IssueResource.getInstance(graph);
+
+               // The main context
+       graph.claim(issue, IR.Issue_HasContext, contexts.get(0));
+       // A possible parent
+               Layer0 L0 = Layer0.getInstance(graph);
+               Resource parent = graph.getPossibleObject(contexts.get(0), L0.PartOf);
+        if(parent != null) {
+               graph.claim(issue, IR.Issue_HasContext, parent);
+        }
+               
+       }    
+    public static void newUserIssue(WriteGraph graph, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+       
+               Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+               if(model == null) throw new DatabaseException("No model for main context");
+               
+               newUserIssueForModel(graph, model, label, severity, contexts);
+               
+    }
+    
+    public static Resource newUserIssueForModel(WriteGraph graph) throws DatabaseException {
+       
+       Resource project = Simantics.getProjectResource();
+       Collection<Resource> activeModels = graph.syncRequest(new ActiveModels(project));
+        if (activeModels.size() != 1)
+            return null;
+
+        IssueResource ISSUE = IssueResource.getInstance(graph);
+        Resource issue = null;
+        for (Resource model : activeModels) {
+            issue = newUserIssueForModel(graph, model, "New User Issue", ISSUE.Severity_Note, Collections.<Resource>emptyList());
+        }
+               return issue;
+        
+    }
+     
+
+    public static Resource newUserIssueForModel(WriteGraph graph, Resource model, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+        IssueResource ISSUE = IssueResource.getInstance(graph);
+       return newUserIssueForModel(graph, model, ISSUE.Issue, label, severity, contexts);
+    }
+
+    public static Resource newUserIssueForModel(WriteGraph graph, Resource model, Resource type, String label, Resource severity, List<Resource> contexts) throws DatabaseException {
+
+        Layer0 L0 = Layer0.getInstance(graph);
+        IssueResource ISSUE = IssueResource.getInstance(graph);
+       
+       Resource issue = graph.newResource();
+               graph.claim(issue, ISSUE.UserIssue, ISSUE.UserIssue, issue);
+               graph.claim(issue, L0.InstanceOf, null, type);
+               graph.claim(issue, ISSUE.Issue_HasSeverity, null, severity);
+               graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));
+               writeAdditionalContext(graph, issue, contexts);
+               graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);
+               graph.claimLiteral(issue, L0.HasLabel, label, Bindings.STRING);
+               DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
+               Object time = format.format(new Date());
+               graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);
+               graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);
+               return issue;
+       
+    }
+    
+    /**
+     * Creates a new issue based on SimpleIssue structure.
+     */
+    public static void newSimpleIssueForModel(WriteGraph graph, Resource model, Resource issueType, List<Resource> contexts, SimpleIssue simpleIssue) throws DatabaseException {
+
+        Layer0 L0 = Layer0.getInstance(graph);
+        IssueResource ISSUE = IssueResource.getInstance(graph);
+        
+        Resource issue = graph.newResource();
+        graph.claim(issue, L0.InstanceOf, null, issueType);
+        graph.claim(issue, ISSUE.Issue_HasSeverity, null, toSeverityResource(ISSUE, simpleIssue.severity));
+        graph.claim(issue, ISSUE.Issue_HasContexts, ListUtils.create(graph, L0.List, contexts));
+        writeAdditionalContext(graph, issue, contexts);
+        graph.claimLiteral(issue, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING);
+        graph.claimLiteral(issue, L0.HasLabel, simpleIssue.label, Bindings.STRING);
+        DateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
+        Object time = format.format(new Date());
+        graph.claimLiteral(issue, ISSUE.Issue_creationTime, time, Bindings.STRING);
+        graph.claim(model, L0.ConsistsOf, L0.PartOf, issue);
+        
+    }
+    
+    /**
+     * Returns the set of all issues with given type.
+     */
+    public static Set<SimpleIssue> getSimpleIssues(ReadGraph graph, List<Resource> contexts, Resource issueType)
+            throws DatabaseException {
+        Layer0 L0 = Layer0.getInstance(graph);
+        IssueResource ISSUE = IssueResource.getInstance(graph);
+        
+        THashSet<SimpleIssue> currentIssues = null;
+        for(Resource issue : graph.getObjects(contexts.get(0), ISSUE.Issue_HasContext_Inverse)) {
+            if(!graph.isInstanceOf(issue, issueType))
+                continue;
+            List<Resource> curContexts = 
+                    ListUtils.toList(graph, graph.getSingleObject(issue, ISSUE.Issue_HasContexts));
+            if(!contexts.equals(curContexts))
+                continue;
+            if(currentIssues == null)
+                currentIssues = new THashSet<SimpleIssue>();
+            currentIssues.add(new SimpleIssue(
+                    (String)graph.getRelatedValue(issue, L0.HasLabel),
+                    toSeverity(ISSUE, graph.getSingleObject(issue, ISSUE.Issue_HasSeverity)), 
+                    issue));
+        }
+        if(currentIssues == null)
+            return Collections.emptySet();
+        else
+            return currentIssues;
+    }
+    
+    /**
+     * Creates and removes issues so that after the operation, 
+     * the context has exactly the given issues with the given type.
+     */
+    public static void setSimpleIssues(WriteGraph graph, Resource model, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {
+        Set<SimpleIssue> currentIssues = getSimpleIssues(graph, contexts, issueType);
+        for(SimpleIssue newIssue : issues) {
+            if(currentIssues.contains(newIssue))
+                currentIssues.remove(newIssue);
+            else
+                newSimpleIssueForModel(graph, model, issueType, contexts, newIssue);
+        }
+        for(SimpleIssue oldIssue : currentIssues)
+            RemoverUtil.remove(graph, oldIssue.issueResource);
+    }
+    
+    /**
+     * Creates and removes issues so that after the operation, 
+     * the context has exactly the given issues with the given type.
+     */
+    public static void setSimpleIssues(WriteGraph graph, List<Resource> contexts, Resource issueType, SimpleIssue ... issues) throws DatabaseException {
+        Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+        if(model == null) throw new DatabaseException("No model for main context");
+     
+        setSimpleIssues(graph, model, contexts, issueType, issues);
+    }
+    
+    /**
+     * Creates and removes issues so that after the operation, 
+     * the context has exactly the given issues with the given type.
+     * Because this method is called in read transaction, it 
+     * makes a write transaction if necessary and continues the
+     * operation there.
+     */
+    public static void setSimpleIssuesAsync(ReadGraph graph,
+            final List<Resource> contexts,
+            final Resource issueType, 
+            final SimpleIssue ... issues) throws DatabaseException {
+        Resource model = graph.sync(new PossibleModel(contexts.get(0)));
+        if(model == null) throw new DatabaseException("No model for main context");
+     
+        setSimpleIssuesAsync(graph, model, contexts, issueType, issues);
+    }
+    
+    /**
+     * Creates and removes issues so that after the operation, 
+     * the context has exactly the given issues with the given type.
+     * Because this method is called in read transaction, it 
+     * makes a write transaction if necessary and continues the
+     * operation there.
+     */
+    public static void setSimpleIssuesAsync(ReadGraph graph, 
+            final Resource model, 
+            final List<Resource> contexts,
+            final Resource issueType, 
+            final SimpleIssue ... issues) throws DatabaseException {
+        Set<SimpleIssue> oldIssues = getSimpleIssues(graph, contexts, issueType);
+        
+        boolean needsUpdating = false;
+        if(issues.length != oldIssues.size())
+            needsUpdating = true;
+        else 
+            for(SimpleIssue newIssue : issues) {
+                if(!oldIssues.contains(newIssue)) {
+                    needsUpdating = true;
+                    break;
+                }
+            }
+        if(needsUpdating) {
+            VirtualGraphSupport support = graph.getService(VirtualGraphSupport.class);
+            VirtualGraph vg = support.getWorkspacePersistent(IssueConstants.ISSUE_VG);
+            Simantics.getSession().asyncRequest(new WriteRequest(vg) {
+                
+                @Override
+                public void perform(WriteGraph graph) throws DatabaseException {
+                    setSimpleIssues(graph, model, contexts, issueType, issues);
+                }
+            });
+        }
+    }
+    
+       public static Resource addIssueSource(WriteGraph g, Resource model, Resource sourceType, String name) throws DatabaseException {
+        Layer0 L0 = Layer0.getInstance(g);
+        Layer0X L0X = Layer0X.getInstance(g);
+
+        Resource source = g.newResource();
+        g.claim(source, L0.InstanceOf, null, sourceType);
+        g.addLiteral(source, L0.HasName, L0.NameOf, L0.String, name, Bindings.STRING);
+        g.claim(source, L0X.IsActivatedBy, L0X.Activates, model);
+        g.claim(source, L0.PartOf, L0.ConsistsOf, model);
+        return source;
+    }
+
+    public static String pathString(String uri, int startIndex) {
+        StringBuilder sb = new StringBuilder(uri.length() - startIndex + 1);
+        sb.append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
+        while (true) {
+            int nextSlash = uri.indexOf(URIStringUtils.NAMESPACE_PATH_SEPARATOR, startIndex);
+            if (nextSlash == -1) {
+                sb.append(URIStringUtils.unescape(uri.substring(startIndex, uri.length())));
+                break;
+            }
+            sb.append(URIStringUtils.unescape(uri.substring(startIndex, nextSlash))).append(URIStringUtils.NAMESPACE_PATH_SEPARATOR);
+            startIndex = nextSlash + 1;
+        }
+        return sb.toString();
+    }
+
+}