]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java
Merge commit '728147df5d63a3333daff3d8c0e9bfd4f5597e3a'
[simantics/platform.git] / bundles / org.simantics.project / src / org / simantics / project / ProjectFeatures.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.project;\r
13 \r
14 import java.util.ArrayList;\r
15 import java.util.Arrays;\r
16 import java.util.Collection;\r
17 import java.util.Collections;\r
18 import java.util.Comparator;\r
19 import java.util.HashMap;\r
20 import java.util.HashSet;\r
21 import java.util.Map;\r
22 import java.util.Set;\r
23 import java.util.TreeSet;\r
24 import java.util.regex.Matcher;\r
25 import java.util.regex.Pattern;\r
26 \r
27 import org.eclipse.core.runtime.IStatus;\r
28 import org.eclipse.core.runtime.Status;\r
29 import org.simantics.databoard.Bindings;\r
30 import org.simantics.db.ReadGraph;\r
31 import org.simantics.db.Resource;\r
32 import org.simantics.db.exception.DatabaseException;\r
33 import org.simantics.project.exception.ProjectException;\r
34 import org.simantics.project.features.registry.GroupReference;\r
35 import org.simantics.project.features.registry.IProjectFeatureExtension;\r
36 import org.simantics.project.features.registry.IProjectFeatureRegistry;\r
37 import org.simantics.project.features.registry.InjectedDependency;\r
38 import org.simantics.project.features.registry.ProjectFeatureReference;\r
39 import org.simantics.project.internal.Activator;\r
40 import org.simantics.project.ontology.ProjectResource;\r
41 import org.simantics.utils.datastructures.MapSet;\r
42 \r
43 /**\r
44  * A facade for handling project feature -related tasks.\r
45  * \r
46  * @author Tuukka Lehtonen\r
47  */\r
48 public class ProjectFeatures {\r
49 \r
50     /**\r
51      * @return the singleton project feature registry service\r
52      */\r
53     public static IProjectFeatureRegistry getRegistry() {\r
54         return Activator.getDefault().getProjectFeatureRegistry();\r
55     }\r
56 \r
57     /**\r
58      * @return\r
59      */\r
60     public static IProjectFeatureExtension[] getAllProjectFeatures() {\r
61         return getRegistry().getExtensions();\r
62     }\r
63 \r
64     /**\r
65      * @return\r
66      */\r
67     public static Collection<IProjectFeatureExtension> getPublishedProjectFeatures() {\r
68         IProjectFeatureExtension[] extensions = getAllProjectFeatures();\r
69         Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();\r
70         for (IProjectFeatureExtension ext : extensions) {\r
71             if (ext.isPublished())\r
72                 result.add(ext);\r
73         }\r
74         return result;\r
75     }\r
76 \r
77     /**\r
78      * @return\r
79      */\r
80     public static Set<GroupReference> getInstallGroupsOfPublishedFeatures() {\r
81         return getInstallGroups( getPublishedProjectFeatures() );\r
82     }\r
83 \r
84     /**\r
85      * @param extensions\r
86      * @return\r
87      */\r
88     public static Set<GroupReference> getInstallGroups(Collection<IProjectFeatureExtension> extensions) {\r
89         Set<GroupReference> result = new TreeSet<GroupReference>();\r
90         for (IProjectFeatureExtension ext : extensions)\r
91             for (GroupReference grp : ext.installGroups())\r
92                 result.add(grp);\r
93         return result;\r
94     }\r
95 \r
96     /**\r
97      * @param filter\r
98      * @return\r
99      */\r
100     public static Collection<IProjectFeatureExtension> filterPublishedFeatures(GroupFilter filter) {\r
101         return filterExtensions( getPublishedProjectFeatures(), filter );\r
102     }\r
103 \r
104     private static Set<IProjectFeatureExtension> filterExtensions(Collection<IProjectFeatureExtension> extensions,\r
105             GroupFilter filter) {\r
106         Set<IProjectFeatureExtension> included = new HashSet<IProjectFeatureExtension>();\r
107         for (IProjectFeatureExtension ext : extensions)\r
108             if (acceptExtension(ext, filter))\r
109                 included.add(ext);\r
110         return included;\r
111     }\r
112 \r
113     private static boolean acceptExtension(IProjectFeatureExtension extension, GroupFilter filter) {\r
114         for (GroupReference grp : extension.installGroups())\r
115             if (filter.accept(grp))\r
116                 return true;\r
117         return false;\r
118     }\r
119 \r
120     /**\r
121      * @param g\r
122      * @param project\r
123      * @return\r
124      * @throws DatabaseException\r
125      * @throws ProjectException \r
126      */\r
127     public static Collection<IProjectFeatureExtension> getTopologicallySortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException {\r
128         return new FeatureWalker().getSortedFeatures(filter);\r
129     }\r
130 \r
131     /**\r
132      * @param g\r
133      * @param project\r
134      * @return\r
135      * @throws DatabaseException\r
136      * @throws ProjectException \r
137      */\r
138     public static Collection<IProjectFeatureExtension> getTopologicallySortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {\r
139         return new FeatureWalker().getSortedFeatures(g, project);\r
140     }\r
141 \r
142     private static class FeatureWalker {\r
143 \r
144         private Map<String, IProjectFeatureExtension>                            byId;\r
145         private final MapSet<IProjectFeatureExtension, IProjectFeatureExtension> required = new MapSet.Hash<IProjectFeatureExtension, IProjectFeatureExtension>();\r
146 \r
147         private IProjectFeatureExtension getExtension(ProjectFeatureReference ref) {\r
148             return byId.get(ref.id);\r
149         }\r
150 \r
151         public Collection<IProjectFeatureExtension> getSortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {\r
152             Set<GroupReference> projectDefinedFeatures = loadFeatureReferences(g, project);\r
153             return getSortedFeatures( GroupFilters.includesVersion( projectDefinedFeatures ) );\r
154         }\r
155 \r
156         public Collection<IProjectFeatureExtension> getSortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException {\r
157             // Create ID -> Extension cache\r
158             IProjectFeatureExtension[] allExtensions = getAllProjectFeatures();\r
159             byId = createIdMap(allExtensions);\r
160 \r
161             // Find which extensions are to be loaded for the feature contexts.\r
162             Set<IProjectFeatureExtension> included = filterExtensions(Arrays.asList(allExtensions), filter);\r
163 \r
164             // Add all required extensions\r
165             Collection<IProjectFeatureExtension> required = requiredExtensions(allExtensions, included);\r
166 \r
167             // Sort topologically\r
168             return sortTopologically(required);\r
169         }\r
170 \r
171         private boolean deepRequires(Set<IProjectFeatureExtension> visited, IProjectFeatureExtension e1, IProjectFeatureExtension e2) {\r
172             // Prevent eternal looping in cyclic cases\r
173             // which are obviously erroneous definitions\r
174             // but we don't want to get stuck anyway.\r
175             if (visited.add(e1)) {\r
176                 // Check direct requires first.\r
177                 Set<IProjectFeatureExtension> req = required.getValues(e1);\r
178                 if (req == null)\r
179                     return false;\r
180                 if (req.contains(e2))\r
181                     return true;\r
182                 // Check transitively requirements\r
183                 for (IProjectFeatureExtension r : req) {\r
184                     if (deepRequires(visited, r, e2))\r
185                         return true;\r
186                 }\r
187             }\r
188             return false;\r
189         }\r
190 \r
191         private Collection<IProjectFeatureExtension> sortTopologically(Collection<IProjectFeatureExtension> toSort) {\r
192             ArrayList<IProjectFeatureExtension> sorted = new ArrayList<IProjectFeatureExtension>(toSort);\r
193             Collections.sort(sorted, new Comparator<IProjectFeatureExtension>() {\r
194                 Set<IProjectFeatureExtension> visited = new HashSet<IProjectFeatureExtension>();\r
195                 @Override\r
196                 public int compare(IProjectFeatureExtension e1, IProjectFeatureExtension e2) {\r
197                     visited.clear();\r
198                     if (deepRequires(visited, e1, e2))\r
199                         return 1;\r
200                     visited.clear();\r
201                     if (deepRequires(visited, e2, e1))\r
202                         return -1;\r
203                     return 0;\r
204                 }\r
205             });\r
206             return sorted;\r
207         }\r
208 \r
209         private Collection<IProjectFeatureExtension> requiredExtensions(IProjectFeatureExtension[] allExtensions, Collection<IProjectFeatureExtension> includedExtensions) throws ProjectException {\r
210             Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();\r
211             Set<IProjectFeatureExtension> visited = new HashSet<IProjectFeatureExtension>();\r
212             for (IProjectFeatureExtension ext : includedExtensions) {\r
213                 walkRequired(result, visited, ext);\r
214             }\r
215 \r
216             Set<String> includedProjectFeatureIds = new HashSet<String>();\r
217             for (IProjectFeatureExtension ext : result) {\r
218                 includedProjectFeatureIds.add(ext.getId());\r
219             }\r
220 \r
221             // Find all injected dependencies!\r
222             boolean changed = false;\r
223             Set<IProjectFeatureExtension> injectionsProcessedFor = new HashSet<IProjectFeatureExtension>();\r
224             do {\r
225                 changed = false;\r
226                 for (IProjectFeatureExtension ext : allExtensions) {\r
227                     if (injectionsProcessedFor.contains(ext))\r
228                         continue;\r
229 \r
230                     for (InjectedDependency injection : ext.injections()) {\r
231                         if (includedProjectFeatureIds.contains(injection.to.id)) {\r
232                             injectionsProcessedFor.add(ext);\r
233                             IProjectFeatureExtension injectionTargetExt = byId.get(injection.to.id);\r
234                             if (injectionTargetExt != null) {\r
235                                 changed = true;\r
236                                 includedProjectFeatureIds.add(ext.getId());\r
237                                 result.add(ext);\r
238                                 required.add(injectionTargetExt, ext);\r
239                             }\r
240                         }\r
241                     }\r
242                 }\r
243             } while (changed);\r
244 \r
245             return result;\r
246         }\r
247 \r
248         private void walkRequired(Collection<IProjectFeatureExtension> result, Set<IProjectFeatureExtension> visited, IProjectFeatureExtension extension) throws ProjectException {\r
249             if (visited.add(extension)) {\r
250                 result.add(extension);\r
251                 for (ProjectFeatureReference ref : extension.requires()) {\r
252                     IProjectFeatureExtension reqExt = getExtension(ref);\r
253                     if (reqExt != null) {\r
254                         required.add(extension, reqExt);\r
255                         walkRequired(result, visited, reqExt);\r
256                     } else if (!ref.optional) {\r
257                         // This requirement was not optional, must report error!\r
258                         throw new ProjectException("Missing org.simantics.project.feature extension with id '" + ref.id + "' required by feature '" + extension.getId() + "'");\r
259                     }\r
260                 }\r
261             }\r
262         }\r
263     }\r
264 \r
265     private static Set<GroupReference> loadFeatureReferences(ReadGraph graph, Resource project) throws DatabaseException {\r
266         ProjectResource PROJ = ProjectResource.getInstance(graph);\r
267         Set<GroupReference> features = new HashSet<GroupReference>();\r
268         for (Resource fs : graph.getObjects(project, PROJ.HasFeature)) {\r
269             String refStr = graph.getRelatedValue(fs, PROJ.HasGroupId, Bindings.STRING);\r
270             try {\r
271                 GroupReference ref = parseFeatureReference(refStr);\r
272                 features.add(ref);\r
273             } catch (IllegalArgumentException e) {\r
274                 // Parse error!\r
275                 e.printStackTrace();\r
276             }\r
277         }\r
278 \r
279         // See http://dev.simantics.org/index.php/Project_Development#Omnipresent_Project_Features\r
280         features.add(GroupReference.OMNIPRESENT);\r
281 \r
282         return features;\r
283     }\r
284 \r
285     private static Map<String, IProjectFeatureExtension> createIdMap(IProjectFeatureExtension[] extensions) {\r
286         Map<String, IProjectFeatureExtension> byId = new HashMap<String, IProjectFeatureExtension>();\r
287         for (IProjectFeatureExtension ext : extensions) {\r
288             IProjectFeatureExtension e = byId.put(ext.getId(), ext);\r
289             if (e != null) {\r
290                 Activator.getDefault().getLog().log(\r
291                         new Status(IStatus.WARNING, Activator.PLUGIN_ID,\r
292                                 "Multiple org.simantics.project.feature extensions defined with id '" + ext.getId()\r
293                                         + "'.\nprevious: " + e + "\nnew: " + ext)\r
294                         );\r
295             }\r
296         }\r
297         return byId;\r
298     }\r
299 \r
300     static String ID_PATTERN_STRING = "[a-zA-Z_0-9]+(?:\\.[a-zA-Z_0-9]+)*";\r
301     static String VERSION_PATTERN_STRING = ".*";\r
302     static Pattern POSSIBLY_VERSIONED_ID_PATTERN = Pattern.compile("(" + ID_PATTERN_STRING + ")(?:/(" + VERSION_PATTERN_STRING + "))?");\r
303 \r
304     /**\r
305      * @param reference\r
306      * @return\r
307      * @throws IllegalArgumentException if the reference string cannot be parsed\r
308      */\r
309     private static GroupReference parseFeatureReference(String reference) {\r
310         Matcher m = POSSIBLY_VERSIONED_ID_PATTERN.matcher(reference);\r
311         if (m.matches()) {\r
312             return new GroupReference(m.group(1), m.group(2));\r
313         } else {\r
314             throw new IllegalArgumentException("could not parse feature reference " + reference);\r
315         }\r
316     }\r
317 \r
318 }\r