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