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