migrate to the new version of format
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / StudySerializationUtils.java
index 83b00b10d66b829065108ab5a355fb02a130f41a..b1ab9e68f83df8abbc8d859e715f7007921c5029 100644 (file)
@@ -1,6 +1,7 @@
 package com.jetbrains.edu.learning;
 
 import com.google.gson.*;
+import com.google.gson.reflect.TypeToken;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.EditorFactory;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -8,19 +9,19 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.hash.HashMap;
 import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jdom.Attribute;
 import org.jdom.Element;
 import org.jdom.output.XMLOutputter;
-import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.lang.reflect.Type;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -30,6 +31,10 @@ public class StudySerializationUtils {
   public static final String PLACEHOLDERS = "placeholders";
   public static final String LINE = "line";
   public static final String START = "start";
+  public static final String LENGTH = "length";
+  public static final String POSSIBLE_ANSWER = "possible_answer";
+  public static final String HINT = "hint";
+  public static final String ADDITIONAL_HINTS = "additional_hints";
   public static final String OFFSET = "offset";
   public static final String TEXT = "text";
   public static final String LESSONS = "lessons";
@@ -43,6 +48,9 @@ public class StudySerializationUtils {
   private StudySerializationUtils() {
   }
 
+  public static class StudyUnrecognizedFormatException extends Exception {
+  }
+
   public static class Xml {
     public final static String COURSE_ELEMENT = "courseElement";
     public final static String MAIN_ELEMENT = "StudyTaskManager";
@@ -64,6 +72,7 @@ public class StudySerializationUtils {
     public static final String MY_LINE = "myLine";
     public static final String MY_START = "myStart";
     public static final String MY_LENGTH = "myLength";
+    public static final String HINT = "hint";
     public static final String AUTHOR_TITLED = "Author";
     public static final String FIRST_NAME = "first_name";
     public static final String SECOND_NAME = "second_name";
@@ -73,11 +82,18 @@ public class StudySerializationUtils {
     public static final String TASK_WINDOWS = "taskWindows";
     public static final String RESOURCE_PATH = "resourcePath";
     public static final String COURSE_DIRECTORY = "courseDirectory";
+    public static final String SUBTASK_INFO = "AnswerPlaceholderSubtaskInfo";
+    public static final String SUBTASK_INFOS = "subtaskInfos";
+    public static final String ADDITIONAL_HINTS = "additionalHints";
+    public static final String POSSIBLE_ANSWER = "possibleAnswer";
+    public static final String SELECTED = "selected";
+    public static final String TASK_TEXT = "taskText";
+    public static final String PLACEHOLDER_TEXT = "placeholderText";
 
     private Xml() {
     }
 
-    public static int getVersion(Element element) {
+    public static int getVersion(Element element) throws StudyUnrecognizedFormatException {
       if (element.getChild(COURSE_ELEMENT) != null) {
         return 1;
       }
@@ -89,10 +105,10 @@ public class StudySerializationUtils {
         return -1;
       }
 
-      return Integer.valueOf(versionElement.getAttributeValue("value"));
+      return Integer.valueOf(versionElement.getAttributeValue(VALUE));
     }
 
-    public static Element convertToSecondVersion(Element element) {
+    public static Element convertToSecondVersion(Element element) throws StudyUnrecognizedFormatException {
       final Element oldCourseElement = element.getChild(COURSE_ELEMENT);
       Element state = new Element(MAIN_ELEMENT);
 
@@ -108,7 +124,7 @@ public class StudySerializationUtils {
       addChildWithName(authorElement, FIRST_NAME, names[0]);
       addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]);
 
-      addChildList(course, AUTHORS, Arrays.asList(authorElement));
+      addChildList(course, AUTHORS, Collections.singletonList(authorElement));
 
       Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH);
       renameElement(courseDirectoryElement, COURSE_DIRECTORY);
@@ -130,7 +146,6 @@ public class StudySerializationUtils {
               addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE));
             }
           }
-
         }
       }
       element.removeContent();
@@ -138,7 +153,8 @@ public class StudySerializationUtils {
       return element;
     }
 
-    public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter) {
+    public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter)
+      throws StudyUnrecognizedFormatException {
       Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
       Map<String, String> destMap = new HashMap<>();
       for (Map.Entry<Element, String> entry : sourceMap.entrySet()) {
@@ -151,7 +167,7 @@ public class StudySerializationUtils {
       return destMap;
     }
 
-    public static Element convertToThirdVersion(Element state, Project project) {
+    public static Element convertToThirdVersion(Element state, Project project) throws StudyUnrecognizedFormatException {
       Element taskManagerElement = state.getChild(MAIN_ELEMENT);
       XMLOutputter outputter = new XMLOutputter();
 
@@ -190,6 +206,31 @@ public class StudySerializationUtils {
       return state;
     }
 
+    public static Element convertToForthVersion(Element state) throws StudyUnrecognizedFormatException {
+      Element taskManagerElement = state.getChild(MAIN_ELEMENT);
+      Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
+      for (Element lesson : getChildList(courseElement, LESSONS)) {
+        for (Element task : getChildList(lesson, TASK_LIST)) {
+          Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
+          for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
+            Element taskFileElement = entry.getValue();
+            for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
+              Element valueElement = new Element(SUBTASK_INFO);
+              addChildMap(placeholder, SUBTASK_INFOS, Collections.singletonMap(String.valueOf(0), valueElement));
+              for (String childName : ContainerUtil
+                .list(HINT, ADDITIONAL_HINTS, POSSIBLE_ANSWER, SELECTED, STATUS, TASK_TEXT)) {
+                Element child = getChildWithName(placeholder, childName);
+                valueElement.addContent(child.clone());
+              }
+              renameElement(getChildWithName(valueElement, TASK_TEXT), PLACEHOLDER_TEXT);
+            }
+          }
+        }
+      }
+
+      return state;
+    }
+
     public static String addStatus(XMLOutputter outputter,
                                    Map<String, String> placeholderTextToStatus,
                                    String taskStatus,
@@ -205,7 +246,7 @@ public class StudySerializationUtils {
       return taskStatus;
     }
 
-    public static void addInitialState(Document document, Element placeholder) {
+    public static void addInitialState(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
       Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE);
       int initialLine = getAsInt(initialState, MY_LINE);
       int initialStart = getAsInt(initialState, MY_START);
@@ -214,18 +255,18 @@ public class StudySerializationUtils {
       renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH);
     }
 
-    public static void addOffset(Document document, Element placeholder) {
+    public static void addOffset(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
       int line = getAsInt(placeholder, LINE);
       int start = getAsInt(placeholder, START);
       int offset = document.getLineStartOffset(line) + start;
       addChildWithName(placeholder, OFFSET, offset);
     }
 
-    public static int getAsInt(Element element, String name) {
+    public static int getAsInt(Element element, String name) throws StudyUnrecognizedFormatException {
       return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE));
     }
 
-    public static void incrementIndex(Element element) {
+    public static void incrementIndex(Element element) throws StudyUnrecognizedFormatException {
       Element index = getChildWithName(element, INDEX);
       int indexValue = Integer.parseInt(index.getAttributeValue(VALUE));
       changeValue(index, indexValue + 1);
@@ -263,8 +304,26 @@ public class StudySerializationUtils {
       return addChildWithName(parent, name, listElement);
     }
 
-    public static List<Element> getChildList(Element parent, String name) {
-      Element listParent = getChildWithName(parent, name);
+    public static Element addChildMap(Element parent, String name, Map<String, Element> value) {
+      Element mapElement = new Element(MAP);
+      for (Map.Entry<String, Element> entry : value.entrySet()) {
+        Element entryElement = new Element("entry");
+        mapElement.addContent(entryElement);
+        String key = entry.getKey();
+        entryElement.setAttribute("key", key);
+        Element valueElement = new Element("value");
+        valueElement.addContent(entry.getValue());
+        entryElement.addContent(valueElement);
+      }
+      return addChildWithName(parent, name, mapElement);
+    }
+
+    public static List<Element> getChildList(Element parent, String name) throws StudyUnrecognizedFormatException {
+      return getChildList(parent, name, false);
+    }
+
+    public static List<Element> getChildList(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
+      Element listParent = getChildWithName(parent, name, optional);
       if (listParent != null) {
         Element list = listParent.getChild(LIST);
         if (list != null) {
@@ -274,8 +333,11 @@ public class StudySerializationUtils {
       return Collections.emptyList();
     }
 
-    @Nullable
-    public static Element getChildWithName(Element parent, String name) {
+    public static Element getChildWithName(Element parent, String name) throws StudyUnrecognizedFormatException {
+      return getChildWithName(parent, name, false);
+    }
+
+    public static Element getChildWithName(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
       for (Element child : parent.getChildren()) {
         Attribute attribute = child.getAttribute(NAME);
         if (attribute == null) {
@@ -285,11 +347,18 @@ public class StudySerializationUtils {
           return child;
         }
       }
-      return null;
+      if (optional) {
+        return null;
+      }
+      throw new StudyUnrecognizedFormatException();
+    }
+
+    public static <K, V> Map<K, V> getChildMap(Element element, String name) throws StudyUnrecognizedFormatException {
+      return getChildMap(element, name, false);
     }
 
-    public static <K, V> Map<K, V> getChildMap(Element element, String name) {
-      Element mapParent = getChildWithName(element, name);
+    public static <K, V> Map<K, V> getChildMap(Element element, String name, boolean optional) throws StudyUnrecognizedFormatException {
+      Element mapParent = getChildWithName(element, name, optional);
       if (mapParent != null) {
         Element map = mapParent.getChild(MAP);
         if (map != null) {
@@ -334,7 +403,7 @@ public class StudySerializationUtils {
             for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
               String name = taskFile.getKey();
               String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
-              VirtualFile resourceFile = LocalFileSystem.getInstance().findFileByIoFile(new File(filePath));
+              VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
               if (resourceFile == null) {
                 continue;
               }
@@ -365,6 +434,7 @@ public class StudySerializationUtils {
 
       @Override
       public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+        final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
         JsonObject taskFileObject = json.getAsJsonObject();
         JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
         for (JsonElement placeholder : placeholders) {
@@ -373,12 +443,61 @@ public class StudySerializationUtils {
           int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
           if (line == -1) {
             placeholderObject.addProperty(OFFSET, start);
-          } else {
+          }
+          else {
             Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString());
             placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start);
           }
+          final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString();
+          final JsonArray hintsArray = new JsonArray();
+
+          try {
+            final Type listType = new TypeToken<List<String>>() {}.getType();
+            final List<String> hints = gson.fromJson(hintString, listType);
+            if (hints != null && !hints.isEmpty()) {
+              for (int i = 0; i < hints.size(); i++) {
+                if (i == 0) {
+                  placeholderObject.addProperty(HINT, hints.get(0));
+                  continue;
+                }
+                hintsArray.add(hints.get(i));
+              }
+              placeholderObject.add(ADDITIONAL_HINTS, hintsArray);
+            }
+            else {
+              placeholderObject.addProperty(HINT, "");
+            }
+          }
+          catch (JsonParseException e) {
+            hintsArray.add(hintString);
+          }
         }
-        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().fromJson(json, TaskFile.class);
+
+        return gson.fromJson(json, TaskFile.class);
+      }
+    }
+
+    public static class StepicAnswerPlaceholderAdapter implements JsonSerializer<AnswerPlaceholder> {
+      @Override
+      public JsonElement serialize(AnswerPlaceholder src, Type typeOfSrc, JsonSerializationContext context) {
+        final List<String> hints = src.getHints();
+
+        final int length = src.getLength();
+        final int start = src.getOffset();
+        final String possibleAnswer = src.getPossibleAnswer();
+        int line = -1;
+
+        final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+        final JsonObject answerPlaceholder = new JsonObject();
+        answerPlaceholder.addProperty(LINE, line);
+        answerPlaceholder.addProperty(START, start);
+        answerPlaceholder.addProperty(LENGTH, length);
+        answerPlaceholder.addProperty(POSSIBLE_ANSWER, possibleAnswer);
+
+        final String jsonHints = gson.toJson(hints);
+        answerPlaceholder.addProperty(HINT, jsonHints);
+
+        return answerPlaceholder;
       }
     }
   }