--- /dev/null
+/*******************************************************************************\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