package LinGUIne.serialization;
import java.util.HashMap;
import java.util.Map.Entry;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import LinGUIne.model.AnnotationSetContents;
import LinGUIne.model.annotations.IAnnotation;
import LinGUIne.model.annotations.MetaAnnotation;
import LinGUIne.model.annotations.Tag;
import LinGUIne.model.annotations.TextAnnotation;
/**
* Used to serialize/deserialize AnnotationSets to/from JSON.
*
* @author Kyle Mullins
*/
public class AnnotationSetTranslator {
private final static String TAGS_ATTRIB = "Tags";
private final static String ANNOTATIONS_ATTRIB = "Annotations";
/**
* Converts the given AnnotationSetContents to Json format so that it may
* be written to disk.
*
* @param contents The AnnotationSetContents object to be converted.
*
* @return A Json String representing the given AnnotationSetContents.
*/
public static String toJson(AnnotationSetContents contents){
JsonElement json = composeJsonFromContents(contents);
return json.toString();
}
/**
* Converts the given Json String into an AnnotationSetContents object.
*
* @param jsonStr A Json String to be converted to an in-memory object.
*
* @return An AnnotationSetContents object representing the given Json
* or null if the Json could not be parsed.
*/
public static AnnotationSetContents fromJson(String jsonStr){
JsonParser parser = new JsonParser();
JsonElement json = parser.parse(jsonStr);
return parseContentsFromJson(json);
}
/*
* Composing into Json
*/
/**
* Returns the root JsonElement of the given AnnotationSetContents object.
*/
private static JsonElement composeJsonFromContents(AnnotationSetContents contents){
JsonObject contentsRootObj = new JsonObject();
JsonArray tagsArray = new JsonArray();
JsonArray annotationsArray = new JsonArray();
HashMap<Tag, Integer> tags = new HashMap<Tag, Integer>();
int id = 1;
for(Tag tag: contents.getTags()){
tags.put(tag, id);
tagsArray.add(composeJsonFromTag(tag, id));
if(!contents.getAnnotations(tag).isEmpty()){
JsonObject annotationRootObj = new JsonObject();
JsonArray locationsArray = new JsonArray();
annotationRootObj.addProperty("TagID", id);
annotationRootObj.add("Locations", locationsArray);
for(IAnnotation annotation: contents.getAnnotations(tag)){
locationsArray.add(composeJsonFromAnnotation(annotation, tags));
}
annotationsArray.add(annotationRootObj);
}
id++;
}
contentsRootObj.add(TAGS_ATTRIB, tagsArray);
contentsRootObj.add(ANNOTATIONS_ATTRIB, annotationsArray);
return contentsRootObj;
}
/**
* Returns the root JsonElement of the given Tag object, assigning it the
* provided id.
*/
private static JsonElement composeJsonFromTag(Tag tag, int id){
JsonObject tagRootObj = new JsonObject();
JsonArray colorArray = new JsonArray();
colorArray.add(new JsonPrimitive(tag.getColor().getRed()));
colorArray.add(new JsonPrimitive(tag.getColor().getGreen()));
colorArray.add(new JsonPrimitive(tag.getColor().getBlue()));
tagRootObj.addProperty("ID", id);
tagRootObj.addProperty("Tagname", tag.getName());
tagRootObj.add("Color", colorArray);
if(tag.getComment() != null){
tagRootObj.addProperty("Description", tag.getComment());
}
return tagRootObj;
}
/**
* Returns the JsonElement of the Location entry corresponding to the given
* IAnnotation object. The tags map is used to look up the ids associated
* with different Tags.
*/
private static JsonElement composeJsonFromAnnotation(IAnnotation annotation,
HashMap<Tag, Integer> tags){
JsonElement locationElem = null;
//TODO: Make all locations objects and add a 'Type' property
if(annotation instanceof MetaAnnotation){
MetaAnnotation metaAnnotation = (MetaAnnotation)annotation;
locationElem = new JsonPrimitive(tags.get(metaAnnotation.getAnnotatedTag()));
}
else if(annotation instanceof TextAnnotation){
TextAnnotation textAnnotation = (TextAnnotation)annotation;
JsonObject locationObj = new JsonObject();
locationObj.addProperty("Start", textAnnotation.getStartIndex());
locationObj.addProperty("End", textAnnotation.getEndIndex());
locationElem = locationObj;
}
return locationElem;
}
/*
* Parsing from Json
*/
/**
* Returns the AnnotationSetContents object described by the given Json
* root object.
*/
private static AnnotationSetContents parseContentsFromJson(JsonElement json){
AnnotationSetContents newContents = new AnnotationSetContents();
HashMap<Integer, Tag> tags = new HashMap<Integer, Tag>();
if(json.isJsonObject()){
JsonObject jsonRootObj = json.getAsJsonObject();
for(Entry<String, JsonElement> entry: jsonRootObj.entrySet()){
if(entry.getKey().equalsIgnoreCase(TAGS_ATTRIB)){
JsonArray tagsArray = entry.getValue().getAsJsonArray();
for(JsonElement tagJson: tagsArray){
if(!parseTagFromJson(tagJson, tags, newContents)){
//TODO: Error handling
return null;
}
}
}
else if(entry.getKey().equalsIgnoreCase(ANNOTATIONS_ATTRIB)){
JsonArray annotationsArray = entry.getValue().getAsJsonArray();
for(JsonElement annotationJson: annotationsArray){
if(!parseAnnotationsFromJson(annotationJson, tags, newContents)){
//TODO: Error handling
return null;
}
}
}
}
}
else{
return null;
}
return newContents;
}
/**
* Parses the Tag described by the given JsonElement and adds it to both
* the tags map and the AnnotationSetContents instance being constructed.
*/
private static boolean parseTagFromJson(JsonElement json,
HashMap<Integer, Tag> tags, AnnotationSetContents contents){
if(json.isJsonObject()){
JsonObject tagRootObj = json.getAsJsonObject();
try{
int id = tagRootObj.get("ID").getAsInt();
String tagname = tagRootObj.get("Tagname").getAsString();
String description = null;
if(tagRootObj.has("Description")){
description = tagRootObj.get("Description").getAsString();
}
JsonArray colorArray = tagRootObj.get("Color").getAsJsonArray();
Color color = new Color(Display.getCurrent(),
colorArray.get(0).getAsInt(),
colorArray.get(1).getAsInt(),
colorArray.get(2).getAsInt());
Tag newTag = new Tag(tagname, color, description);
tags.put(id, newTag);
contents.addTag(newTag);
return true;
}
catch(ClassCastException | IllegalStateException e){
//Couldn't parse the Tag
return false;
}
}
return false;
}
/**
* Parses all of the Annotations described by the given JsonElement and
* adds all of them to the AnnotationSetContents instance being constructed.
* The tags map is used to look up ids associated with different Tags.
*/
private static boolean parseAnnotationsFromJson(JsonElement json,
HashMap<Integer, Tag> tags, AnnotationSetContents contents){
if(json.isJsonObject()){
JsonObject annotationRootObj = json.getAsJsonObject();
try{
int tagId = annotationRootObj.get("TagID").getAsInt();
JsonArray locationsArray = annotationRootObj.get("Locations").
getAsJsonArray();
if(!tags.containsKey(tagId)){
//TagID doesn't correspond to an existing Tag
return false;
}
Tag myTag = tags.get(tagId);
for(JsonElement location: locationsArray){
IAnnotation newAnnotation = null;
//TODO: Make all locations objects and add a 'Type' property
if(location.isJsonObject()){
//Standard TextAnnotation
JsonObject locationObject = location.getAsJsonObject();
int start = locationObject.get("Start").getAsInt();
int end = locationObject.get("End").getAsInt();
newAnnotation = new TextAnnotation(myTag, start,
end - start);
}
else if(location.isJsonPrimitive()){
//MetaAnnotation
int annotatedTagId = location.getAsInt();
if(!tags.containsKey(annotatedTagId)){
//TagID doesn't correspond to an existing Tag
return false;
}
Tag annotatedTag = tags.get(annotatedTagId);
newAnnotation = new MetaAnnotation(myTag, annotatedTag);
}
else{
return false;
}
contents.addAnnotation(newAnnotation);
}
return true;
}
catch(ClassCastException | IllegalStateException e){
//Couldn't parse the Annotation
return false;
}
}
return false;
}
}