X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.project%2Fsrc%2Forg%2Fsimantics%2Fproject%2FProjectFeatures.java;fp=bundles%2Forg.simantics.project%2Fsrc%2Forg%2Fsimantics%2Fproject%2FProjectFeatures.java;h=7fedc55d11ae8a5713f83e6e5315dbc192cb4e7b;hp=1a1982bdad7307ccb79409731b477c8d8db63c8d;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java b/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java index 1a1982bda..7fedc55d1 100644 --- a/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java +++ b/bundles/org.simantics.project/src/org/simantics/project/ProjectFeatures.java @@ -1,318 +1,318 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management - * in Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.project; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.simantics.databoard.Bindings; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.exception.DatabaseException; -import org.simantics.project.exception.ProjectException; -import org.simantics.project.features.registry.GroupReference; -import org.simantics.project.features.registry.IProjectFeatureExtension; -import org.simantics.project.features.registry.IProjectFeatureRegistry; -import org.simantics.project.features.registry.InjectedDependency; -import org.simantics.project.features.registry.ProjectFeatureReference; -import org.simantics.project.internal.Activator; -import org.simantics.project.ontology.ProjectResource; -import org.simantics.utils.datastructures.MapSet; - -/** - * A facade for handling project feature -related tasks. - * - * @author Tuukka Lehtonen - */ -public class ProjectFeatures { - - /** - * @return the singleton project feature registry service - */ - public static IProjectFeatureRegistry getRegistry() { - return Activator.getDefault().getProjectFeatureRegistry(); - } - - /** - * @return - */ - public static IProjectFeatureExtension[] getAllProjectFeatures() { - return getRegistry().getExtensions(); - } - - /** - * @return - */ - public static Collection getPublishedProjectFeatures() { - IProjectFeatureExtension[] extensions = getAllProjectFeatures(); - Collection result = new ArrayList(); - for (IProjectFeatureExtension ext : extensions) { - if (ext.isPublished()) - result.add(ext); - } - return result; - } - - /** - * @return - */ - public static Set getInstallGroupsOfPublishedFeatures() { - return getInstallGroups( getPublishedProjectFeatures() ); - } - - /** - * @param extensions - * @return - */ - public static Set getInstallGroups(Collection extensions) { - Set result = new TreeSet(); - for (IProjectFeatureExtension ext : extensions) - for (GroupReference grp : ext.installGroups()) - result.add(grp); - return result; - } - - /** - * @param filter - * @return - */ - public static Collection filterPublishedFeatures(GroupFilter filter) { - return filterExtensions( getPublishedProjectFeatures(), filter ); - } - - private static Set filterExtensions(Collection extensions, - GroupFilter filter) { - Set included = new HashSet(); - for (IProjectFeatureExtension ext : extensions) - if (acceptExtension(ext, filter)) - included.add(ext); - return included; - } - - private static boolean acceptExtension(IProjectFeatureExtension extension, GroupFilter filter) { - for (GroupReference grp : extension.installGroups()) - if (filter.accept(grp)) - return true; - return false; - } - - /** - * @param g - * @param project - * @return - * @throws DatabaseException - * @throws ProjectException - */ - public static Collection getTopologicallySortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException { - return new FeatureWalker().getSortedFeatures(filter); - } - - /** - * @param g - * @param project - * @return - * @throws DatabaseException - * @throws ProjectException - */ - public static Collection getTopologicallySortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException { - return new FeatureWalker().getSortedFeatures(g, project); - } - - private static class FeatureWalker { - - private Map byId; - private final MapSet required = new MapSet.Hash(); - - private IProjectFeatureExtension getExtension(ProjectFeatureReference ref) { - return byId.get(ref.id); - } - - public Collection getSortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException { - Set projectDefinedFeatures = loadFeatureReferences(g, project); - return getSortedFeatures( GroupFilters.includesVersion( projectDefinedFeatures ) ); - } - - public Collection getSortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException { - // Create ID -> Extension cache - IProjectFeatureExtension[] allExtensions = getAllProjectFeatures(); - byId = createIdMap(allExtensions); - - // Find which extensions are to be loaded for the feature contexts. - Set included = filterExtensions(Arrays.asList(allExtensions), filter); - - // Add all required extensions - Collection required = requiredExtensions(allExtensions, included); - - // Sort topologically - return sortTopologically(required); - } - - private boolean deepRequires(Set visited, IProjectFeatureExtension e1, IProjectFeatureExtension e2) { - // Prevent eternal looping in cyclic cases - // which are obviously erroneous definitions - // but we don't want to get stuck anyway. - if (visited.add(e1)) { - // Check direct requires first. - Set req = required.getValues(e1); - if (req == null) - return false; - if (req.contains(e2)) - return true; - // Check transitively requirements - for (IProjectFeatureExtension r : req) { - if (deepRequires(visited, r, e2)) - return true; - } - } - return false; - } - - private Collection sortTopologically(Collection toSort) { - ArrayList sorted = new ArrayList(toSort); - Collections.sort(sorted, new Comparator() { - Set visited = new HashSet(); - @Override - public int compare(IProjectFeatureExtension e1, IProjectFeatureExtension e2) { - visited.clear(); - if (deepRequires(visited, e1, e2)) - return 1; - visited.clear(); - if (deepRequires(visited, e2, e1)) - return -1; - return 0; - } - }); - return sorted; - } - - private Collection requiredExtensions(IProjectFeatureExtension[] allExtensions, Collection includedExtensions) throws ProjectException { - Collection result = new ArrayList(); - Set visited = new HashSet(); - for (IProjectFeatureExtension ext : includedExtensions) { - walkRequired(result, visited, ext); - } - - Set includedProjectFeatureIds = new HashSet(); - for (IProjectFeatureExtension ext : result) { - includedProjectFeatureIds.add(ext.getId()); - } - - // Find all injected dependencies! - boolean changed = false; - Set injectionsProcessedFor = new HashSet(); - do { - changed = false; - for (IProjectFeatureExtension ext : allExtensions) { - if (injectionsProcessedFor.contains(ext)) - continue; - - for (InjectedDependency injection : ext.injections()) { - if (includedProjectFeatureIds.contains(injection.to.id)) { - injectionsProcessedFor.add(ext); - IProjectFeatureExtension injectionTargetExt = byId.get(injection.to.id); - if (injectionTargetExt != null) { - changed = true; - includedProjectFeatureIds.add(ext.getId()); - result.add(ext); - required.add(injectionTargetExt, ext); - } - } - } - } - } while (changed); - - return result; - } - - private void walkRequired(Collection result, Set visited, IProjectFeatureExtension extension) throws ProjectException { - if (visited.add(extension)) { - result.add(extension); - for (ProjectFeatureReference ref : extension.requires()) { - IProjectFeatureExtension reqExt = getExtension(ref); - if (reqExt != null) { - required.add(extension, reqExt); - walkRequired(result, visited, reqExt); - } else if (!ref.optional) { - // This requirement was not optional, must report error! - throw new ProjectException("Missing org.simantics.project.feature extension with id '" + ref.id + "' required by feature '" + extension.getId() + "'"); - } - } - } - } - } - - private static Set loadFeatureReferences(ReadGraph graph, Resource project) throws DatabaseException { - ProjectResource PROJ = ProjectResource.getInstance(graph); - Set features = new HashSet(); - for (Resource fs : graph.getObjects(project, PROJ.HasFeature)) { - String refStr = graph.getRelatedValue(fs, PROJ.HasGroupId, Bindings.STRING); - try { - GroupReference ref = parseFeatureReference(refStr); - features.add(ref); - } catch (IllegalArgumentException e) { - // Parse error! - e.printStackTrace(); - } - } - - // See http://dev.simantics.org/index.php/Project_Development#Omnipresent_Project_Features - features.add(GroupReference.OMNIPRESENT); - - return features; - } - - private static Map createIdMap(IProjectFeatureExtension[] extensions) { - Map byId = new HashMap(); - for (IProjectFeatureExtension ext : extensions) { - IProjectFeatureExtension e = byId.put(ext.getId(), ext); - if (e != null) { - Activator.getDefault().getLog().log( - new Status(IStatus.WARNING, Activator.PLUGIN_ID, - "Multiple org.simantics.project.feature extensions defined with id '" + ext.getId() - + "'.\nprevious: " + e + "\nnew: " + ext) - ); - } - } - return byId; - } - - static String ID_PATTERN_STRING = "[a-zA-Z_0-9]+(?:\\.[a-zA-Z_0-9]+)*"; - static String VERSION_PATTERN_STRING = ".*"; - static Pattern POSSIBLY_VERSIONED_ID_PATTERN = Pattern.compile("(" + ID_PATTERN_STRING + ")(?:/(" + VERSION_PATTERN_STRING + "))?"); - - /** - * @param reference - * @return - * @throws IllegalArgumentException if the reference string cannot be parsed - */ - private static GroupReference parseFeatureReference(String reference) { - Matcher m = POSSIBLY_VERSIONED_ID_PATTERN.matcher(reference); - if (m.matches()) { - return new GroupReference(m.group(1), m.group(2)); - } else { - throw new IllegalArgumentException("could not parse feature reference " + reference); - } - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.project; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.project.exception.ProjectException; +import org.simantics.project.features.registry.GroupReference; +import org.simantics.project.features.registry.IProjectFeatureExtension; +import org.simantics.project.features.registry.IProjectFeatureRegistry; +import org.simantics.project.features.registry.InjectedDependency; +import org.simantics.project.features.registry.ProjectFeatureReference; +import org.simantics.project.internal.Activator; +import org.simantics.project.ontology.ProjectResource; +import org.simantics.utils.datastructures.MapSet; + +/** + * A facade for handling project feature -related tasks. + * + * @author Tuukka Lehtonen + */ +public class ProjectFeatures { + + /** + * @return the singleton project feature registry service + */ + public static IProjectFeatureRegistry getRegistry() { + return Activator.getDefault().getProjectFeatureRegistry(); + } + + /** + * @return + */ + public static IProjectFeatureExtension[] getAllProjectFeatures() { + return getRegistry().getExtensions(); + } + + /** + * @return + */ + public static Collection getPublishedProjectFeatures() { + IProjectFeatureExtension[] extensions = getAllProjectFeatures(); + Collection result = new ArrayList(); + for (IProjectFeatureExtension ext : extensions) { + if (ext.isPublished()) + result.add(ext); + } + return result; + } + + /** + * @return + */ + public static Set getInstallGroupsOfPublishedFeatures() { + return getInstallGroups( getPublishedProjectFeatures() ); + } + + /** + * @param extensions + * @return + */ + public static Set getInstallGroups(Collection extensions) { + Set result = new TreeSet(); + for (IProjectFeatureExtension ext : extensions) + for (GroupReference grp : ext.installGroups()) + result.add(grp); + return result; + } + + /** + * @param filter + * @return + */ + public static Collection filterPublishedFeatures(GroupFilter filter) { + return filterExtensions( getPublishedProjectFeatures(), filter ); + } + + private static Set filterExtensions(Collection extensions, + GroupFilter filter) { + Set included = new HashSet(); + for (IProjectFeatureExtension ext : extensions) + if (acceptExtension(ext, filter)) + included.add(ext); + return included; + } + + private static boolean acceptExtension(IProjectFeatureExtension extension, GroupFilter filter) { + for (GroupReference grp : extension.installGroups()) + if (filter.accept(grp)) + return true; + return false; + } + + /** + * @param g + * @param project + * @return + * @throws DatabaseException + * @throws ProjectException + */ + public static Collection getTopologicallySortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException { + return new FeatureWalker().getSortedFeatures(filter); + } + + /** + * @param g + * @param project + * @return + * @throws DatabaseException + * @throws ProjectException + */ + public static Collection getTopologicallySortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException { + return new FeatureWalker().getSortedFeatures(g, project); + } + + private static class FeatureWalker { + + private Map byId; + private final MapSet required = new MapSet.Hash(); + + private IProjectFeatureExtension getExtension(ProjectFeatureReference ref) { + return byId.get(ref.id); + } + + public Collection getSortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException { + Set projectDefinedFeatures = loadFeatureReferences(g, project); + return getSortedFeatures( GroupFilters.includesVersion( projectDefinedFeatures ) ); + } + + public Collection getSortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException { + // Create ID -> Extension cache + IProjectFeatureExtension[] allExtensions = getAllProjectFeatures(); + byId = createIdMap(allExtensions); + + // Find which extensions are to be loaded for the feature contexts. + Set included = filterExtensions(Arrays.asList(allExtensions), filter); + + // Add all required extensions + Collection required = requiredExtensions(allExtensions, included); + + // Sort topologically + return sortTopologically(required); + } + + private boolean deepRequires(Set visited, IProjectFeatureExtension e1, IProjectFeatureExtension e2) { + // Prevent eternal looping in cyclic cases + // which are obviously erroneous definitions + // but we don't want to get stuck anyway. + if (visited.add(e1)) { + // Check direct requires first. + Set req = required.getValues(e1); + if (req == null) + return false; + if (req.contains(e2)) + return true; + // Check transitively requirements + for (IProjectFeatureExtension r : req) { + if (deepRequires(visited, r, e2)) + return true; + } + } + return false; + } + + private Collection sortTopologically(Collection toSort) { + ArrayList sorted = new ArrayList(toSort); + Collections.sort(sorted, new Comparator() { + Set visited = new HashSet(); + @Override + public int compare(IProjectFeatureExtension e1, IProjectFeatureExtension e2) { + visited.clear(); + if (deepRequires(visited, e1, e2)) + return 1; + visited.clear(); + if (deepRequires(visited, e2, e1)) + return -1; + return 0; + } + }); + return sorted; + } + + private Collection requiredExtensions(IProjectFeatureExtension[] allExtensions, Collection includedExtensions) throws ProjectException { + Collection result = new ArrayList(); + Set visited = new HashSet(); + for (IProjectFeatureExtension ext : includedExtensions) { + walkRequired(result, visited, ext); + } + + Set includedProjectFeatureIds = new HashSet(); + for (IProjectFeatureExtension ext : result) { + includedProjectFeatureIds.add(ext.getId()); + } + + // Find all injected dependencies! + boolean changed = false; + Set injectionsProcessedFor = new HashSet(); + do { + changed = false; + for (IProjectFeatureExtension ext : allExtensions) { + if (injectionsProcessedFor.contains(ext)) + continue; + + for (InjectedDependency injection : ext.injections()) { + if (includedProjectFeatureIds.contains(injection.to.id)) { + injectionsProcessedFor.add(ext); + IProjectFeatureExtension injectionTargetExt = byId.get(injection.to.id); + if (injectionTargetExt != null) { + changed = true; + includedProjectFeatureIds.add(ext.getId()); + result.add(ext); + required.add(injectionTargetExt, ext); + } + } + } + } + } while (changed); + + return result; + } + + private void walkRequired(Collection result, Set visited, IProjectFeatureExtension extension) throws ProjectException { + if (visited.add(extension)) { + result.add(extension); + for (ProjectFeatureReference ref : extension.requires()) { + IProjectFeatureExtension reqExt = getExtension(ref); + if (reqExt != null) { + required.add(extension, reqExt); + walkRequired(result, visited, reqExt); + } else if (!ref.optional) { + // This requirement was not optional, must report error! + throw new ProjectException("Missing org.simantics.project.feature extension with id '" + ref.id + "' required by feature '" + extension.getId() + "'"); + } + } + } + } + } + + private static Set loadFeatureReferences(ReadGraph graph, Resource project) throws DatabaseException { + ProjectResource PROJ = ProjectResource.getInstance(graph); + Set features = new HashSet(); + for (Resource fs : graph.getObjects(project, PROJ.HasFeature)) { + String refStr = graph.getRelatedValue(fs, PROJ.HasGroupId, Bindings.STRING); + try { + GroupReference ref = parseFeatureReference(refStr); + features.add(ref); + } catch (IllegalArgumentException e) { + // Parse error! + e.printStackTrace(); + } + } + + // See http://dev.simantics.org/index.php/Project_Development#Omnipresent_Project_Features + features.add(GroupReference.OMNIPRESENT); + + return features; + } + + private static Map createIdMap(IProjectFeatureExtension[] extensions) { + Map byId = new HashMap(); + for (IProjectFeatureExtension ext : extensions) { + IProjectFeatureExtension e = byId.put(ext.getId(), ext); + if (e != null) { + Activator.getDefault().getLog().log( + new Status(IStatus.WARNING, Activator.PLUGIN_ID, + "Multiple org.simantics.project.feature extensions defined with id '" + ext.getId() + + "'.\nprevious: " + e + "\nnew: " + ext) + ); + } + } + return byId; + } + + static String ID_PATTERN_STRING = "[a-zA-Z_0-9]+(?:\\.[a-zA-Z_0-9]+)*"; + static String VERSION_PATTERN_STRING = ".*"; + static Pattern POSSIBLY_VERSIONED_ID_PATTERN = Pattern.compile("(" + ID_PATTERN_STRING + ")(?:/(" + VERSION_PATTERN_STRING + "))?"); + + /** + * @param reference + * @return + * @throws IllegalArgumentException if the reference string cannot be parsed + */ + private static GroupReference parseFeatureReference(String reference) { + Matcher m = POSSIBLY_VERSIONED_ID_PATTERN.matcher(reference); + if (m.matches()) { + return new GroupReference(m.group(1), m.group(2)); + } else { + throw new IllegalArgumentException("could not parse feature reference " + reference); + } + } + +}