Skip to content

Commit

Permalink
Added feature model serialization for export
Browse files Browse the repository at this point in the history
  • Loading branch information
ekuiter committed Nov 19, 2019
1 parent f67e5e2 commit 47534e8
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 28 deletions.
2 changes: 2 additions & 0 deletions client/src/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ const actions = {
({type: MessageType.ADD_ARTIFACT, artifactPath, source})),
removeArtifact: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) =>
({type: MessageType.REMOVE_ARTIFACT, artifactPath})),
exportArtifact: createMessageAction(({artifactPath, format}: {artifactPath: ArtifactPath, format: string}) =>
({type: MessageType.EXPORT_ARTIFACT, artifactPath, format})),
joinRequest: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) => ({type: MessageType.JOIN_REQUEST, artifactPath})),
leaveRequest: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) => ({type: MessageType.LEAVE_REQUEST, artifactPath})),
undo: createMessageAction(() => ({type: MessageType.ERROR})), // TODO
Expand Down
1 change: 1 addition & 0 deletions client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum MessageType {
SET_USER_PROFILE = 'SET_USER_PROFILE',
ADD_ARTIFACT = 'ADD_ARTIFACT',
REMOVE_ARTIFACT = 'REMOVE_ARTIFACT',
EXPORT_ARTIFACT = 'EXPORT_ARTIFACT',
JOIN_REQUEST = 'JOIN_REQUEST',
LEAVE_REQUEST = 'LEAVE_REQUEST',
INITIALIZE = 'INITIALIZE',
Expand Down
4 changes: 0 additions & 4 deletions server/src/main/java/de/ovgu/spldev/varied/Artifact.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ public static class FeatureModel extends Artifact {
this(project, name, () -> FeatureModelUtils.loadFeatureModel(path));
}

FeatureModel(Project project, String name, URL url) throws URISyntaxException {
this(project, name, Paths.get(url.toURI()));
}

FeatureModel(Project project, String name, IFeatureModel initialFeatureModel) {
this(project, name, () -> initialFeatureModel);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package de.ovgu.spldev.varied;

import de.ovgu.featureide.fm.core.base.IFeatureModel;
import de.ovgu.featureide.fm.core.base.IFeatureModelFactory;
import de.ovgu.featureide.fm.core.base.impl.DefaultFeatureModelFactory;
import de.ovgu.featureide.fm.core.base.impl.FeatureModel;
import de.ovgu.spldev.varied.kernel.Kernel;
import de.ovgu.spldev.varied.messaging.Api;
import de.ovgu.spldev.varied.messaging.Message;
import de.ovgu.spldev.varied.util.CollaboratorUtils;
import de.ovgu.spldev.varied.util.FeatureModelUtils;
import org.pmw.tinylog.Logger;

import java.util.*;
Expand Down Expand Up @@ -71,6 +75,10 @@ static class FeatureModel extends CollaborativeSession {
this.kernel = new Kernel(artifactPath, initialFeatureModel);
}

public IFeatureModel toFeatureModel() {
return kernel.toFeatureModel();
}

private void broadcastResponse(Collaborator collaborator, Object[] involvedSiteIDsAndMessage) {
String[] involvedSiteIDs = (String[]) involvedSiteIDsAndMessage[0];
String newMessage = (String) involvedSiteIDsAndMessage[1];
Expand Down Expand Up @@ -135,6 +143,12 @@ protected boolean _onMessage(Collaborator collaborator, Message.IDecodable messa
return true;
}

if (message instanceof Api.ExportArtifact) {
Api.ExportArtifact exportArtifactMessage = (Api.ExportArtifact) message;
exportArtifactMessage.data = FeatureModelUtils.serializeFeatureModel(toFeatureModel(), exportArtifactMessage.format);
collaborator.send(exportArtifactMessage);
}

return false;
}

Expand Down
12 changes: 4 additions & 8 deletions server/src/main/java/de/ovgu/spldev/varied/Collaborator.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,10 @@ void onMessage(Message message) throws Message.InvalidMessageException {
}
String source = ((Api.AddArtifact) message).source;
Artifact artifact;
if (source == null) {
try {
artifact = new Artifact.FeatureModel(project, artifactPath.getArtifactName(),
Resources.getResource("examples/" + ProjectManager.EMPTY + ".xml"));
} catch (URISyntaxException e) {
throw new RuntimeException("invalid resource path given");
}
} else
if (source == null)
artifact = new Artifact.FeatureModel(project, artifactPath.getArtifactName(),
ProjectManager.getResourcePath("examples/" + ProjectManager.EMPTY + ".xml"));
else
artifact = new Artifact.FeatureModel(project, artifactPath.getArtifactName(), source);
project.addArtifact(artifact);
CollaboratorManager.getInstance().broadcast(new Api.AddArtifact(Arrays.asList(artifactPath)));
Expand Down
12 changes: 9 additions & 3 deletions server/src/main/java/de/ovgu/spldev/varied/ProjectManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.Scanner;
Expand Down Expand Up @@ -98,15 +100,19 @@ void addRemoteArtifact(Project project, String artifactName, String url) {
}));
}

void addExampleArtifact(Project project, String artifactName) {
public static Path getResourcePath(String fileName) {
try {
project.addArtifact(new Artifact.FeatureModel(project, artifactName,
Resources.getResource("examples/" + artifactName + ".xml")));
return Paths.get(Resources.getResource(fileName).toURI());
} catch (URISyntaxException e) {
throw new RuntimeException("invalid resource path given");
}
}

void addExampleArtifact(Project project, String artifactName) {
project.addArtifact(new Artifact.FeatureModel(project, artifactName,
getResourcePath("examples/" + artifactName + ".xml")));
}

public void removeProject(Project project) {
Logger.info("removing project {}", project);
projects.remove(project.getName().toLowerCase());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package de.ovgu.spldev.varied.kernel;

import clojure.lang.PersistentHashMap;
import de.ovgu.featureide.fm.core.base.FeatureUtils;
import de.ovgu.featureide.fm.core.base.IConstraint;
import de.ovgu.featureide.fm.core.base.IFeature;
import de.ovgu.featureide.fm.core.base.IFeatureModel;
import clojure.lang.*;
import de.ovgu.featureide.fm.core.base.*;
import de.ovgu.featureide.fm.core.base.impl.DefaultFeatureModelFactory;
import de.ovgu.featureide.fm.core.io.UnsupportedModelException;
import org.prop4j.*;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import static de.ovgu.featureide.fm.core.io.xml.XMLFeatureModelTags.CONJ;
import static de.ovgu.featureide.fm.core.io.xml.XMLFeatureModelTags.DISJ;
import static de.ovgu.featureide.fm.core.localization.StringTable.NOT;

// adapted from de.ovgu.featureide.fm.core.io.xml.XmlFeatureModelFormat
public class FeatureModelFormat {
public static PersistentHashMap toKernel(IFeatureModel featureModel) {
public static APersistentMap toKernel(IFeatureModel featureModel) {
HashMap<Object, Object> featureModelMap = new HashMap<>(),
featuresMap = new HashMap<>(),
constraintsMap = new HashMap<>();
Expand Down Expand Up @@ -41,7 +49,7 @@ else if (feature.getStructure().isAlternative())
featureMap.put(Kernel.keyword("description"), description.replace("\r", ""));
} else
featureMap.put(Kernel.keyword("description"), null);
featuresMap.put(feature.getName(), Kernel.toPersistentHashMap(featureMap));
featuresMap.put(feature.getName(), Kernel.toPersistentMap(featureMap));
});

for (final IConstraint constraint : featureModel.getConstraints()) {
Expand All @@ -53,12 +61,12 @@ else if (feature.getStructure().isAlternative())
constraintMap.put(Kernel.keyword("formula"), formulaList.get(0));
constraintMap.put(Kernel.keyword("graveyarded?"), false);
constraintsMap.put(de.ovgu.spldev.varied.util.FeatureUtils.getConstraintID(constraint).toString(),
Kernel.toPersistentHashMap(constraintMap));
Kernel.toPersistentMap(constraintMap));
}

featureModelMap.put(Kernel.keyword("features"), Kernel.toPersistentHashMap(featuresMap));
featureModelMap.put(Kernel.keyword("constraints"), Kernel.toPersistentHashMap(constraintsMap));
return Kernel.toPersistentHashMap(featureModelMap);
featureModelMap.put(Kernel.keyword("features"), Kernel.toPersistentMap(featuresMap));
featureModelMap.put(Kernel.keyword("constraints"), Kernel.toPersistentMap(constraintsMap));
return Kernel.toPersistentMap(featureModelMap);
}

private static void createConstraint(ArrayList<Object> formulaList, org.prop4j.Node node) {
Expand Down Expand Up @@ -95,4 +103,105 @@ else if (node instanceof Not)

formulaList.add(Kernel.toPersistentVector(op));
}

public static IFeatureModel toFeatureModel(Object kernelContext) {
IFeatureModelFactory featureModelFactory = DefaultFeatureModelFactory.getInstance();
IFeatureModel featureModel = featureModelFactory.createFeatureModel();

APersistentMap featuresHashMap, constraintsHashMap, childrenCacheHashMap;
try {
APersistentMap contextHashMap = (APersistentMap) kernelContext;
Atom atom = (Atom) contextHashMap.get(Kernel.keyword("combined-effect"));
APersistentMap featureModelHashMap = (APersistentMap) atom.deref();
featuresHashMap = (APersistentMap) featureModelHashMap.get(Kernel.keyword("features"));
constraintsHashMap = (APersistentMap) featureModelHashMap.get(Kernel.keyword("constraints"));
childrenCacheHashMap = (APersistentMap) featureModelHashMap.get(Kernel.keyword("children-cache"));
} catch (Throwable t) {
throw new RuntimeException("feature model not available in kernel context");
}

parseFeatures(featureModelFactory, featureModel, featuresHashMap, childrenCacheHashMap,
(APersistentSet) childrenCacheHashMap.get(null), null);

for (final Object e : constraintsHashMap) {
IMapEntry entry = (IMapEntry) e;
String constraintID = (String) entry.key();
APersistentMap constraintHashMap = (APersistentMap) entry.val();
Object formula = constraintHashMap.get(Kernel.keyword("formula"));
boolean graveyarded = (boolean) constraintHashMap.get(Kernel.keyword("graveyarded?"));

if (!graveyarded)
try {
featureModel.addConstraint(featureModelFactory.createConstraint(featureModel, parseConstraint(featureModel, formula)));
} catch (GraveyardedFeatureException e1) {
}
}

return featureModel;
}

private static void parseFeatures(IFeatureModelFactory featureModelFactory, IFeatureModel featureModel,
APersistentMap featuresHashMap, APersistentMap childrenCacheHashMap, APersistentSet children, IFeature parent) {
for (final Object child : children) {
String featureID = (String) child;
APersistentMap featureHashMap = (APersistentMap) featuresHashMap.get(featureID);
if (featureModel.getFeature(featureID) != null)
throw new RuntimeException("Duplicate entry for feature: " + featureID);

final IFeature feature = featureModelFactory.createFeature(featureModel, featureID);
String groupType = ((Keyword) featureHashMap.get(Kernel.keyword("group-type"))).getName();
if (groupType.equals("and"))
feature.getStructure().setAnd();
else if (groupType.equals("alternative"))
feature.getStructure().setAlternative();
else if (groupType.equals("or"))
feature.getStructure().setOr();
feature.getStructure().setMandatory(!((boolean) featureHashMap.get(Kernel.keyword("optional?"))));
feature.getStructure().setAbstract((boolean) featureHashMap.get(Kernel.keyword("abstract?")));
feature.getStructure().setHidden((boolean) featureHashMap.get(Kernel.keyword("hidden?")));
String description = (String) featureHashMap.get(Kernel.keyword("description"));
if (description != null && !description.trim().isEmpty())
feature.getProperty().setDescription(description.replace("\r", ""));
de.ovgu.spldev.varied.util.FeatureUtils.setFeatureName(feature, (String) featureHashMap.get(Kernel.keyword("name")));

featureModel.addFeature(feature);
if (parent == null)
featureModel.getStructure().setRoot(feature.getStructure());
else
parent.getStructure().addChild(feature.getStructure());

if (childrenCacheHashMap.get(featureID) != null)
parseFeatures(featureModelFactory, featureModel, featuresHashMap, childrenCacheHashMap,
(APersistentSet) childrenCacheHashMap.get(featureID), feature);
}
}

private static Node parseConstraint(IFeatureModel featureModel, Object formula) throws GraveyardedFeatureException {
if (formula instanceof String) {
final String featureID = (String) formula;
if (featureModel.getFeature(featureID) != null)
return new Literal(featureID);
else
throw new GraveyardedFeatureException();
} else if (formula instanceof APersistentVector) {
APersistentVector formulaVector = (APersistentVector) formula;
String op = ((Keyword) formulaVector.get(0)).getName();
Node child1 = parseConstraint(featureModel, formulaVector.get(1));
Node child2 = op.equals("not") ? null : parseConstraint(featureModel, formulaVector.get(2));
if (op.equals("disj"))
return new Or(child1, child2);
else if (op.equals("conj"))
return new And(child1, child2);
else if (op.equals("eq"))
return new Equals(child1, child2);
else if (op.equals("imp"))
return new Implies(child1, child2);
else if (op.equals("not"))
return new Not(child1);
}
return null;
}

private static class GraveyardedFeatureException extends Throwable {
}
}
8 changes: 6 additions & 2 deletions server/src/main/java/de/ovgu/spldev/varied/kernel/Kernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ static Object keyword(String keywordString) {
return Clojure.read(":" + keywordString);
}

static PersistentHashMap toPersistentHashMap(HashMap hashMap) {
return (PersistentHashMap) Clojure.var("clojure.core", "into").invoke(PersistentHashMap.EMPTY, hashMap);
static APersistentMap toPersistentMap(HashMap hashMap) {
return (APersistentMap) Clojure.var("clojure.core", "into").invoke(PersistentHashMap.EMPTY, hashMap);
}

static PersistentVector toPersistentVector(ArrayList arrayList) {
Expand Down Expand Up @@ -82,6 +82,10 @@ public Kernel(Artifact.Path artifactPath, IFeatureModel initialFeatureModel) {
callKernelAtomic("serverInitialize", FeatureModelFormat.toKernel(initialFeatureModel));
}

public IFeatureModel toFeatureModel() {
return FeatureModelFormat.toFeatureModel(context);
}

public String generateHeartbeat() {
return (String) callKernelAtomic("serverGenerateHeartbeat");
}
Expand Down
13 changes: 13 additions & 0 deletions server/src/main/java/de/ovgu/spldev/varied/messaging/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum TypeEnum {
RESET,
ADD_ARTIFACT,
REMOVE_ARTIFACT,
EXPORT_ARTIFACT,
COLLABORATOR_JOINED,
COLLABORATOR_LEFT,
SET_USER_PROFILE,
Expand Down Expand Up @@ -67,6 +68,18 @@ public RemoveArtifact(de.ovgu.spldev.varied.Artifact.Path artifactPath) {
}
}

public static class ExportArtifact extends Message implements Message.IEncodable, Message.IDecodable {
@Expose
public String format;

@Expose
public String data;

public ExportArtifact(de.ovgu.spldev.varied.Artifact.Path artifactPath) {
super(TypeEnum.EXPORT_ARTIFACT, artifactPath);
}
}

public static class CollaboratorJoined extends Message implements Message.IEncodable {
@Expose
Collaborator collaborator;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package de.ovgu.spldev.varied.util;

import de.ovgu.featureide.fm.core.ExtensionManager;
import de.ovgu.featureide.fm.core.PluginID;
import de.ovgu.featureide.fm.core.base.IFeature;
import de.ovgu.featureide.fm.core.base.IFeatureModel;
import de.ovgu.featureide.fm.core.base.impl.FMFormatManager;
import de.ovgu.featureide.fm.core.io.IFeatureModelFormat;
import de.ovgu.featureide.fm.core.io.IPersistentFormat;
import de.ovgu.featureide.fm.core.io.manager.FeatureModelManager;
import org.pmw.tinylog.Logger;
Expand All @@ -20,6 +24,17 @@ private static void renameFeaturesToFeatureIDs(IFeatureModel featureModel) {
FeatureUtils.setConstraintID(constraint, UUID.randomUUID()));
}

private static void renameFeatureIDsToFeatures(IFeatureModel featureModel) {
de.ovgu.featureide.fm.core.base.FeatureUtils.getFeatureNames(featureModel).forEach(featureID -> {
IFeature feature = featureModel.getFeature(featureID);
String featureName = FeatureUtils.getFeatureName(feature);
if (!featureModel.getRenamingsManager().renameFeature(featureID, featureName))
throw new RuntimeException("could not rename feature " + featureID + " to " + featureName);
FeatureUtils.removeFeatureName(feature);
});
featureModel.getConstraints().forEach(FeatureUtils::removeConstraintID);
}

public static IFeatureModel loadFeatureModel(Path path) {
Logger.debug("loading feature model from {}", path);
IFeatureModel featureModel = FeatureModelManager.load(path).getObject();
Expand All @@ -40,4 +55,23 @@ public static IFeatureModel loadFeatureModel(String source, String fileName) {
renameFeaturesToFeatureIDs(featureModel);
return featureModel;
}

public static String serializeFeatureModel(IFeatureModel featureModel, String formatName) {
Logger.debug("serializing feature model with format {}", formatName);
if (featureModel == null)
throw new RuntimeException("no feature model given");
featureModel = featureModel.clone();
renameFeatureIDsToFeatures(featureModel);
IFeatureModelFormat format;
try {
format = FMFormatManager.getInstance().getFormatById(PluginID.PLUGIN_ID + ".format.fm." + formatName);
} catch (ExtensionManager.NoSuchExtensionException e) {
throw new RuntimeException("invalid feature model format given");
}
return format.write(featureModel);
}

public static String serializeFeatureModel(IFeatureModel featureModel) {
return serializeFeatureModel(featureModel, "XmlFeatureModelFormat");
}
}
Loading

0 comments on commit 47534e8

Please sign in to comment.