]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.project / src / org / simantics / project / ProjectFeatures.java
diff --git a/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java b/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java
new file mode 100644 (file)
index 0000000..1a1982b
--- /dev/null
@@ -0,0 +1,318 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 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.project;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.Status;\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.project.exception.ProjectException;\r
+import org.simantics.project.features.registry.GroupReference;\r
+import org.simantics.project.features.registry.IProjectFeatureExtension;\r
+import org.simantics.project.features.registry.IProjectFeatureRegistry;\r
+import org.simantics.project.features.registry.InjectedDependency;\r
+import org.simantics.project.features.registry.ProjectFeatureReference;\r
+import org.simantics.project.internal.Activator;\r
+import org.simantics.project.ontology.ProjectResource;\r
+import org.simantics.utils.datastructures.MapSet;\r
+\r
+/**\r
+ * A facade for handling project feature -related tasks.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class ProjectFeatures {\r
+\r
+    /**\r
+     * @return the singleton project feature registry service\r
+     */\r
+    public static IProjectFeatureRegistry getRegistry() {\r
+        return Activator.getDefault().getProjectFeatureRegistry();\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    public static IProjectFeatureExtension[] getAllProjectFeatures() {\r
+        return getRegistry().getExtensions();\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    public static Collection<IProjectFeatureExtension> getPublishedProjectFeatures() {\r
+        IProjectFeatureExtension[] extensions = getAllProjectFeatures();\r
+        Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();\r
+        for (IProjectFeatureExtension ext : extensions) {\r
+            if (ext.isPublished())\r
+                result.add(ext);\r
+        }\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    public static Set<GroupReference> getInstallGroupsOfPublishedFeatures() {\r
+        return getInstallGroups( getPublishedProjectFeatures() );\r
+    }\r
+\r
+    /**\r
+     * @param extensions\r
+     * @return\r
+     */\r
+    public static Set<GroupReference> getInstallGroups(Collection<IProjectFeatureExtension> extensions) {\r
+        Set<GroupReference> result = new TreeSet<GroupReference>();\r
+        for (IProjectFeatureExtension ext : extensions)\r
+            for (GroupReference grp : ext.installGroups())\r
+                result.add(grp);\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * @param filter\r
+     * @return\r
+     */\r
+    public static Collection<IProjectFeatureExtension> filterPublishedFeatures(GroupFilter filter) {\r
+        return filterExtensions( getPublishedProjectFeatures(), filter );\r
+    }\r
+\r
+    private static Set<IProjectFeatureExtension> filterExtensions(Collection<IProjectFeatureExtension> extensions,\r
+            GroupFilter filter) {\r
+        Set<IProjectFeatureExtension> included = new HashSet<IProjectFeatureExtension>();\r
+        for (IProjectFeatureExtension ext : extensions)\r
+            if (acceptExtension(ext, filter))\r
+                included.add(ext);\r
+        return included;\r
+    }\r
+\r
+    private static boolean acceptExtension(IProjectFeatureExtension extension, GroupFilter filter) {\r
+        for (GroupReference grp : extension.installGroups())\r
+            if (filter.accept(grp))\r
+                return true;\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * @param g\r
+     * @param project\r
+     * @return\r
+     * @throws DatabaseException\r
+     * @throws ProjectException \r
+     */\r
+    public static Collection<IProjectFeatureExtension> getTopologicallySortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException {\r
+        return new FeatureWalker().getSortedFeatures(filter);\r
+    }\r
+\r
+    /**\r
+     * @param g\r
+     * @param project\r
+     * @return\r
+     * @throws DatabaseException\r
+     * @throws ProjectException \r
+     */\r
+    public static Collection<IProjectFeatureExtension> getTopologicallySortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {\r
+        return new FeatureWalker().getSortedFeatures(g, project);\r
+    }\r
+\r
+    private static class FeatureWalker {\r
+\r
+        private Map<String, IProjectFeatureExtension>                            byId;\r
+        private final MapSet<IProjectFeatureExtension, IProjectFeatureExtension> required = new MapSet.Hash<IProjectFeatureExtension, IProjectFeatureExtension>();\r
+\r
+        private IProjectFeatureExtension getExtension(ProjectFeatureReference ref) {\r
+            return byId.get(ref.id);\r
+        }\r
+\r
+        public Collection<IProjectFeatureExtension> getSortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {\r
+            Set<GroupReference> projectDefinedFeatures = loadFeatureReferences(g, project);\r
+            return getSortedFeatures( GroupFilters.includesVersion( projectDefinedFeatures ) );\r
+        }\r
+\r
+        public Collection<IProjectFeatureExtension> getSortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException {\r
+            // Create ID -> Extension cache\r
+            IProjectFeatureExtension[] allExtensions = getAllProjectFeatures();\r
+            byId = createIdMap(allExtensions);\r
+\r
+            // Find which extensions are to be loaded for the feature contexts.\r
+            Set<IProjectFeatureExtension> included = filterExtensions(Arrays.asList(allExtensions), filter);\r
+\r
+            // Add all required extensions\r
+            Collection<IProjectFeatureExtension> required = requiredExtensions(allExtensions, included);\r
+\r
+            // Sort topologically\r
+            return sortTopologically(required);\r
+        }\r
+\r
+        private boolean deepRequires(Set<IProjectFeatureExtension> visited, IProjectFeatureExtension e1, IProjectFeatureExtension e2) {\r
+            // Prevent eternal looping in cyclic cases\r
+            // which are obviously erroneous definitions\r
+            // but we don't want to get stuck anyway.\r
+            if (visited.add(e1)) {\r
+                // Check direct requires first.\r
+                Set<IProjectFeatureExtension> req = required.getValues(e1);\r
+                if (req == null)\r
+                    return false;\r
+                if (req.contains(e2))\r
+                    return true;\r
+                // Check transitively requirements\r
+                for (IProjectFeatureExtension r : req) {\r
+                    if (deepRequires(visited, r, e2))\r
+                        return true;\r
+                }\r
+            }\r
+            return false;\r
+        }\r
+\r
+        private Collection<IProjectFeatureExtension> sortTopologically(Collection<IProjectFeatureExtension> toSort) {\r
+            ArrayList<IProjectFeatureExtension> sorted = new ArrayList<IProjectFeatureExtension>(toSort);\r
+            Collections.sort(sorted, new Comparator<IProjectFeatureExtension>() {\r
+                Set<IProjectFeatureExtension> visited = new HashSet<IProjectFeatureExtension>();\r
+                @Override\r
+                public int compare(IProjectFeatureExtension e1, IProjectFeatureExtension e2) {\r
+                    visited.clear();\r
+                    if (deepRequires(visited, e1, e2))\r
+                        return 1;\r
+                    visited.clear();\r
+                    if (deepRequires(visited, e2, e1))\r
+                        return -1;\r
+                    return 0;\r
+                }\r
+            });\r
+            return sorted;\r
+        }\r
+\r
+        private Collection<IProjectFeatureExtension> requiredExtensions(IProjectFeatureExtension[] allExtensions, Collection<IProjectFeatureExtension> includedExtensions) throws ProjectException {\r
+            Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();\r
+            Set<IProjectFeatureExtension> visited = new HashSet<IProjectFeatureExtension>();\r
+            for (IProjectFeatureExtension ext : includedExtensions) {\r
+                walkRequired(result, visited, ext);\r
+            }\r
+\r
+            Set<String> includedProjectFeatureIds = new HashSet<String>();\r
+            for (IProjectFeatureExtension ext : result) {\r
+                includedProjectFeatureIds.add(ext.getId());\r
+            }\r
+\r
+            // Find all injected dependencies!\r
+            boolean changed = false;\r
+            Set<IProjectFeatureExtension> injectionsProcessedFor = new HashSet<IProjectFeatureExtension>();\r
+            do {\r
+                changed = false;\r
+                for (IProjectFeatureExtension ext : allExtensions) {\r
+                    if (injectionsProcessedFor.contains(ext))\r
+                        continue;\r
+\r
+                    for (InjectedDependency injection : ext.injections()) {\r
+                        if (includedProjectFeatureIds.contains(injection.to.id)) {\r
+                            injectionsProcessedFor.add(ext);\r
+                            IProjectFeatureExtension injectionTargetExt = byId.get(injection.to.id);\r
+                            if (injectionTargetExt != null) {\r
+                                changed = true;\r
+                                includedProjectFeatureIds.add(ext.getId());\r
+                                result.add(ext);\r
+                                required.add(injectionTargetExt, ext);\r
+                            }\r
+                        }\r
+                    }\r
+                }\r
+            } while (changed);\r
+\r
+            return result;\r
+        }\r
+\r
+        private void walkRequired(Collection<IProjectFeatureExtension> result, Set<IProjectFeatureExtension> visited, IProjectFeatureExtension extension) throws ProjectException {\r
+            if (visited.add(extension)) {\r
+                result.add(extension);\r
+                for (ProjectFeatureReference ref : extension.requires()) {\r
+                    IProjectFeatureExtension reqExt = getExtension(ref);\r
+                    if (reqExt != null) {\r
+                        required.add(extension, reqExt);\r
+                        walkRequired(result, visited, reqExt);\r
+                    } else if (!ref.optional) {\r
+                        // This requirement was not optional, must report error!\r
+                        throw new ProjectException("Missing org.simantics.project.feature extension with id '" + ref.id + "' required by feature '" + extension.getId() + "'");\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    private static Set<GroupReference> loadFeatureReferences(ReadGraph graph, Resource project) throws DatabaseException {\r
+        ProjectResource PROJ = ProjectResource.getInstance(graph);\r
+        Set<GroupReference> features = new HashSet<GroupReference>();\r
+        for (Resource fs : graph.getObjects(project, PROJ.HasFeature)) {\r
+            String refStr = graph.getRelatedValue(fs, PROJ.HasGroupId, Bindings.STRING);\r
+            try {\r
+                GroupReference ref = parseFeatureReference(refStr);\r
+                features.add(ref);\r
+            } catch (IllegalArgumentException e) {\r
+                // Parse error!\r
+                e.printStackTrace();\r
+            }\r
+        }\r
+\r
+        // See http://dev.simantics.org/index.php/Project_Development#Omnipresent_Project_Features\r
+        features.add(GroupReference.OMNIPRESENT);\r
+\r
+        return features;\r
+    }\r
+\r
+    private static Map<String, IProjectFeatureExtension> createIdMap(IProjectFeatureExtension[] extensions) {\r
+        Map<String, IProjectFeatureExtension> byId = new HashMap<String, IProjectFeatureExtension>();\r
+        for (IProjectFeatureExtension ext : extensions) {\r
+            IProjectFeatureExtension e = byId.put(ext.getId(), ext);\r
+            if (e != null) {\r
+                Activator.getDefault().getLog().log(\r
+                        new Status(IStatus.WARNING, Activator.PLUGIN_ID,\r
+                                "Multiple org.simantics.project.feature extensions defined with id '" + ext.getId()\r
+                                        + "'.\nprevious: " + e + "\nnew: " + ext)\r
+                        );\r
+            }\r
+        }\r
+        return byId;\r
+    }\r
+\r
+    static String ID_PATTERN_STRING = "[a-zA-Z_0-9]+(?:\\.[a-zA-Z_0-9]+)*";\r
+    static String VERSION_PATTERN_STRING = ".*";\r
+    static Pattern POSSIBLY_VERSIONED_ID_PATTERN = Pattern.compile("(" + ID_PATTERN_STRING + ")(?:/(" + VERSION_PATTERN_STRING + "))?");\r
+\r
+    /**\r
+     * @param reference\r
+     * @return\r
+     * @throws IllegalArgumentException if the reference string cannot be parsed\r
+     */\r
+    private static GroupReference parseFeatureReference(String reference) {\r
+        Matcher m = POSSIBLY_VERSIONED_ID_PATTERN.matcher(reference);\r
+        if (m.matches()) {\r
+            return new GroupReference(m.group(1), m.group(2));\r
+        } else {\r
+            throw new IllegalArgumentException("could not parse feature reference " + reference);\r
+        }\r
+    }\r
+\r
+}\r