-/*******************************************************************************\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
+/*******************************************************************************
+ * 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.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<IProjectFeatureExtension> getPublishedProjectFeatures() {
+ IProjectFeatureExtension[] extensions = getAllProjectFeatures();
+ Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();
+ for (IProjectFeatureExtension ext : extensions) {
+ if (ext.isPublished())
+ result.add(ext);
+ }
+ return result;
+ }
+
+ /**
+ * @return
+ */
+ public static Set<GroupReference> getInstallGroupsOfPublishedFeatures() {
+ return getInstallGroups( getPublishedProjectFeatures() );
+ }
+
+ /**
+ * @param extensions
+ * @return
+ */
+ public static Set<GroupReference> getInstallGroups(Collection<IProjectFeatureExtension> extensions) {
+ Set<GroupReference> result = new TreeSet<GroupReference>();
+ for (IProjectFeatureExtension ext : extensions)
+ for (GroupReference grp : ext.installGroups())
+ result.add(grp);
+ return result;
+ }
+
+ /**
+ * @param filter
+ * @return
+ */
+ public static Collection<IProjectFeatureExtension> filterPublishedFeatures(GroupFilter filter) {
+ return filterExtensions( getPublishedProjectFeatures(), filter );
+ }
+
+ private static Set<IProjectFeatureExtension> filterExtensions(Collection<IProjectFeatureExtension> extensions,
+ GroupFilter filter) {
+ Set<IProjectFeatureExtension> included = new HashSet<IProjectFeatureExtension>();
+ 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<IProjectFeatureExtension> getTopologicallySortedFeatures(GroupFilter filter) throws DatabaseException, ProjectException {
+ return new FeatureWalker().getSortedFeatures(filter);
+ }
+
+ /**
+ * @param g
+ * @param project
+ * @return
+ * @throws DatabaseException
+ * @throws ProjectException
+ */
+ public static Collection<IProjectFeatureExtension> getTopologicallySortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {
+ return new FeatureWalker().getSortedFeatures(g, project);
+ }
+
+ private static class FeatureWalker {
+
+ private Map<String, IProjectFeatureExtension> byId;
+ private final MapSet<IProjectFeatureExtension, IProjectFeatureExtension> required = new MapSet.Hash<IProjectFeatureExtension, IProjectFeatureExtension>();
+
+ private IProjectFeatureExtension getExtension(ProjectFeatureReference ref) {
+ return byId.get(ref.id);
+ }
+
+ public Collection<IProjectFeatureExtension> getSortedFeatures(ReadGraph g, Resource project) throws DatabaseException, ProjectException {
+ Set<GroupReference> projectDefinedFeatures = loadFeatureReferences(g, project);
+ return getSortedFeatures( GroupFilters.includesVersion( projectDefinedFeatures ) );
+ }
+
+ public Collection<IProjectFeatureExtension> 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<IProjectFeatureExtension> included = filterExtensions(Arrays.asList(allExtensions), filter);
+
+ // Add all required extensions
+ Collection<IProjectFeatureExtension> required = requiredExtensions(allExtensions, included);
+
+ // Sort topologically
+ return sortTopologically(required);
+ }
+
+ private boolean deepRequires(Set<IProjectFeatureExtension> 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<IProjectFeatureExtension> 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 void requiresDFS(IProjectFeatureExtension ext, ArrayList<IProjectFeatureExtension> result, Set<IProjectFeatureExtension> visited) {
+ if(visited.add(ext)) {
+ Set<IProjectFeatureExtension> reqs = required.getValues(ext);
+ if(reqs != null) {
+ for(IProjectFeatureExtension req : reqs) {
+ requiresDFS(req, result, visited);
+ }
+ }
+ result.add(ext);
+ }
+ }
+
+ private Collection<IProjectFeatureExtension> sortTopologically(Collection<IProjectFeatureExtension> toSort) {
+ ArrayList<IProjectFeatureExtension> result = new ArrayList<>();
+ Set<IProjectFeatureExtension> visited = new HashSet<>();
+ for(IProjectFeatureExtension ext : toSort) {
+ requiresDFS(ext, result, visited);
+ }
+ return result;
+ }
+
+ private Collection<IProjectFeatureExtension> requiredExtensions(IProjectFeatureExtension[] allExtensions, Collection<IProjectFeatureExtension> includedExtensions) throws ProjectException {
+ Collection<IProjectFeatureExtension> result = new ArrayList<IProjectFeatureExtension>();
+ Set<IProjectFeatureExtension> visited = new HashSet<IProjectFeatureExtension>();
+ for (IProjectFeatureExtension ext : includedExtensions) {
+ walkRequired(result, visited, ext);
+ }
+
+ Set<String> includedProjectFeatureIds = new HashSet<String>();
+ for (IProjectFeatureExtension ext : result) {
+ includedProjectFeatureIds.add(ext.getId());
+ }
+
+ // Find all injected dependencies!
+ boolean changed = false;
+ Set<IProjectFeatureExtension> injectionsProcessedFor = new HashSet<IProjectFeatureExtension>();
+ 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());
+ if(!result.contains(ext))
+ result.add(ext);
+ required.add(injectionTargetExt, ext);
+ }
+ }
+ }
+ }
+ } while (changed);
+
+ return result;
+ }
+
+ private void walkRequired(Collection<IProjectFeatureExtension> result, Set<IProjectFeatureExtension> 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<GroupReference> loadFeatureReferences(ReadGraph graph, Resource project) throws DatabaseException {
+ ProjectResource PROJ = ProjectResource.getInstance(graph);
+ Set<GroupReference> features = new HashSet<GroupReference>();
+ 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<String, IProjectFeatureExtension> createIdMap(IProjectFeatureExtension[] extensions) {
+ Map<String, IProjectFeatureExtension> byId = new HashMap<String, IProjectFeatureExtension>();
+ 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);
+ }
+ }
+
+}