replace subtaskInfo map with list for stepik
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / StudySerializationUtils.java
1 package com.jetbrains.edu.learning;
2
3 import com.google.gson.*;
4 import com.google.gson.reflect.TypeToken;
5 import com.intellij.openapi.editor.Document;
6 import com.intellij.openapi.editor.EditorFactory;
7 import com.intellij.openapi.fileEditor.FileDocumentManager;
8 import com.intellij.openapi.project.Project;
9 import com.intellij.openapi.util.io.FileUtil;
10 import com.intellij.openapi.vfs.LocalFileSystem;
11 import com.intellij.openapi.vfs.VirtualFile;
12 import com.intellij.util.containers.ContainerUtil;
13 import com.intellij.util.containers.hash.HashMap;
14 import com.jetbrains.edu.learning.core.EduNames;
15 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
16 import com.jetbrains.edu.learning.courseFormat.Course;
17 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
18 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
19 import com.jetbrains.edu.learning.stepic.StepicWrappers;
20 import org.jdom.Attribute;
21 import org.jdom.Element;
22 import org.jdom.output.XMLOutputter;
23
24 import java.io.File;
25 import java.lang.reflect.Type;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30
31 public class StudySerializationUtils {
32
33   public static final String PLACEHOLDERS = "placeholders";
34   public static final String LINE = "line";
35   public static final String START = "start";
36   public static final String LENGTH = "length";
37   public static final String POSSIBLE_ANSWER = "possible_answer";
38   public static final String HINT = "hint";
39   public static final String ADDITIONAL_HINTS = "additional_hints";
40   public static final String OFFSET = "offset";
41   public static final String TEXT = "text";
42   public static final String LESSONS = "lessons";
43   public static final String COURSE = "course";
44   public static final String COURSE_TITLED = "Course";
45   public static final String STATUS = "status";
46   public static final String AUTHOR = "author";
47   public static final String AUTHORS = "authors";
48   public static final String MY_INITIAL_START = "myInitialStart";
49
50   private StudySerializationUtils() {
51   }
52
53   public static class StudyUnrecognizedFormatException extends Exception {
54   }
55
56   public static class Xml {
57     public final static String COURSE_ELEMENT = "courseElement";
58     public final static String MAIN_ELEMENT = "StudyTaskManager";
59     public static final String MAP = "map";
60     public static final String KEY = "key";
61     public static final String VALUE = "value";
62     public static final String NAME = "name";
63     public static final String LIST = "list";
64     public static final String OPTION = "option";
65     public static final String INDEX = "index";
66     public static final String STUDY_STATUS_MAP = "myStudyStatusMap";
67     public static final String TASK_STATUS_MAP = "myTaskStatusMap";
68     public static final String LENGTH = "length";
69     public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders";
70     public static final String TASK_LIST = "taskList";
71     public static final String TASK_FILES = "taskFiles";
72     public static final String INITIAL_STATE = "initialState";
73     public static final String MY_INITIAL_STATE = "MyInitialState";
74     public static final String MY_LINE = "myLine";
75     public static final String MY_START = "myStart";
76     public static final String MY_LENGTH = "myLength";
77     public static final String HINT = "hint";
78     public static final String AUTHOR_TITLED = "Author";
79     public static final String FIRST_NAME = "first_name";
80     public static final String SECOND_NAME = "second_name";
81     public static final String MY_INITIAL_LINE = "myInitialLine";
82     public static final String MY_INITIAL_LENGTH = "myInitialLength";
83     public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder";
84     public static final String TASK_WINDOWS = "taskWindows";
85     public static final String RESOURCE_PATH = "resourcePath";
86     public static final String COURSE_DIRECTORY = "courseDirectory";
87     public static final String SUBTASK_INFO = "AnswerPlaceholderSubtaskInfo";
88     public static final String SUBTASK_INFOS = "subtaskInfos";
89     public static final String ADDITIONAL_HINTS = "additionalHints";
90     public static final String POSSIBLE_ANSWER = "possibleAnswer";
91     public static final String SELECTED = "selected";
92     public static final String TASK_TEXT = "taskText";
93     public static final String PLACEHOLDER_TEXT = "placeholderText";
94
95     private Xml() {
96     }
97
98     public static int getVersion(Element element) throws StudyUnrecognizedFormatException {
99       if (element.getChild(COURSE_ELEMENT) != null) {
100         return 1;
101       }
102
103       final Element taskManager = element.getChild(MAIN_ELEMENT);
104
105       Element versionElement = getChildWithName(taskManager, "VERSION");
106       if (versionElement == null) {
107         return -1;
108       }
109
110       return Integer.valueOf(versionElement.getAttributeValue(VALUE));
111     }
112
113     public static Element convertToSecondVersion(Element element) throws StudyUnrecognizedFormatException {
114       final Element oldCourseElement = element.getChild(COURSE_ELEMENT);
115       Element state = new Element(MAIN_ELEMENT);
116
117       Element course = addChildWithName(state, COURSE, oldCourseElement.clone());
118       course.setName(COURSE_TITLED);
119
120       Element author = getChildWithName(course, AUTHOR);
121       String authorString = author.getAttributeValue(VALUE);
122       course.removeContent(author);
123
124       String[] names = authorString.split(" ", 2);
125       Element authorElement = new Element(AUTHOR_TITLED);
126       addChildWithName(authorElement, FIRST_NAME, names[0]);
127       addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]);
128
129       addChildList(course, AUTHORS, Collections.singletonList(authorElement));
130
131       Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH);
132       renameElement(courseDirectoryElement, COURSE_DIRECTORY);
133
134       for (Element lesson : getChildList(course, LESSONS)) {
135         incrementIndex(lesson);
136         for (Element task : getChildList(lesson, TASK_LIST)) {
137           incrementIndex(task);
138           Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
139           for (Element taskFile : taskFiles.values()) {
140             renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS);
141             for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) {
142               placeholder.setName(ANSWER_PLACEHOLDER);
143
144               Element initialState = new Element(MY_INITIAL_STATE);
145               addChildWithName(placeholder, INITIAL_STATE, initialState);
146               addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE));
147               addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE));
148               addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE));
149             }
150           }
151         }
152       }
153       element.removeContent();
154       element.addContent(state);
155       return element;
156     }
157
158     public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter)
159       throws StudyUnrecognizedFormatException {
160       Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
161       Map<String, String> destMap = new HashMap<>();
162       for (Map.Entry<Element, String> entry : sourceMap.entrySet()) {
163         String status = entry.getValue();
164         if (status.equals(StudyStatus.Unchecked.toString())) {
165           continue;
166         }
167         destMap.put(outputter.outputString(entry.getKey()), status);
168       }
169       return destMap;
170     }
171
172     public static Element convertToThirdVersion(Element state, Project project) throws StudyUnrecognizedFormatException {
173       Element taskManagerElement = state.getChild(MAIN_ELEMENT);
174       XMLOutputter outputter = new XMLOutputter();
175
176       Map<String, String> placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter);
177       Map<String, String> taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter);
178
179       Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
180       for (Element lesson : getChildList(courseElement, LESSONS)) {
181         int lessonIndex = getAsInt(lesson, INDEX);
182         for (Element task : getChildList(lesson, TASK_LIST)) {
183           String taskStatus = null;
184           int taskIndex = getAsInt(task, INDEX);
185           Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
186           for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
187             Element taskFileElement = entry.getValue();
188             String taskFileText = outputter.outputString(taskFileElement);
189             String taskFileStatus = taskFileToStatusMap.get(taskFileText);
190             if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) {
191               taskStatus = taskFileStatus;
192             }
193             Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey());
194             if (document == null) {
195               continue;
196             }
197             for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
198               taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder);
199               addOffset(document, placeholder);
200               addInitialState(document, placeholder);
201             }
202           }
203           if (taskStatus != null) {
204             addChildWithName(task, STATUS, taskStatus);
205           }
206         }
207       }
208       return state;
209     }
210
211     public static Element convertToForthVersion(Element state) throws StudyUnrecognizedFormatException {
212       Element taskManagerElement = state.getChild(MAIN_ELEMENT);
213       Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
214       for (Element lesson : getChildList(courseElement, LESSONS)) {
215         for (Element task : getChildList(lesson, TASK_LIST)) {
216           Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
217           for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
218             Element taskFileElement = entry.getValue();
219             for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
220               Element valueElement = new Element(SUBTASK_INFO);
221               addChildMap(placeholder, SUBTASK_INFOS, Collections.singletonMap(String.valueOf(0), valueElement));
222               for (String childName : ContainerUtil
223                 .list(HINT, ADDITIONAL_HINTS, POSSIBLE_ANSWER, SELECTED, STATUS, TASK_TEXT)) {
224                 Element child = getChildWithName(placeholder, childName);
225                 valueElement.addContent(child.clone());
226               }
227               renameElement(getChildWithName(valueElement, TASK_TEXT), PLACEHOLDER_TEXT);
228               List<Element> additionalHints = ContainerUtil.map(getChildList(valueElement, ADDITIONAL_HINTS), Element::clone);
229               Element hint = getChildWithName(valueElement, HINT);
230               Element firstHint = new Element(OPTION).setAttribute(VALUE, hint.getAttributeValue(VALUE));
231               List<Element> newHints = new ArrayList<>();
232               newHints.add(firstHint);
233               newHints.addAll(additionalHints);
234               addChildList(valueElement, "hints", newHints);
235             }
236           }
237         }
238       }
239
240       return state;
241     }
242
243     public static String addStatus(XMLOutputter outputter,
244                                    Map<String, String> placeholderTextToStatus,
245                                    String taskStatus,
246                                    Element placeholder) {
247       String placeholderText = outputter.outputString(placeholder);
248       String status = placeholderTextToStatus.get(placeholderText);
249       if (status != null) {
250         addChildWithName(placeholder, STATUS, status);
251         if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) {
252           taskStatus = status;
253         }
254       }
255       return taskStatus;
256     }
257
258     public static void addInitialState(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
259       Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE);
260       int initialLine = getAsInt(initialState, MY_LINE);
261       int initialStart = getAsInt(initialState, MY_START);
262       int initialOffset = document.getLineStartOffset(initialLine) + initialStart;
263       addChildWithName(initialState, OFFSET, initialOffset);
264       renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH);
265     }
266
267     public static void addOffset(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
268       int line = getAsInt(placeholder, LINE);
269       int start = getAsInt(placeholder, START);
270       int offset = document.getLineStartOffset(line) + start;
271       addChildWithName(placeholder, OFFSET, offset);
272     }
273
274     public static int getAsInt(Element element, String name) throws StudyUnrecognizedFormatException {
275       return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE));
276     }
277
278     public static void incrementIndex(Element element) throws StudyUnrecognizedFormatException {
279       Element index = getChildWithName(element, INDEX);
280       int indexValue = Integer.parseInt(index.getAttributeValue(VALUE));
281       changeValue(index, indexValue + 1);
282     }
283
284     public static void renameElement(Element element, String newName) {
285       element.setAttribute(NAME, newName);
286     }
287
288     public static void changeValue(Element element, Object newValue) {
289       element.setAttribute(VALUE, newValue.toString());
290     }
291
292     public static Element addChildWithName(Element parent, String name, Element value) {
293       Element child = new Element(OPTION);
294       child.setAttribute(NAME, name);
295       child.addContent(value);
296       parent.addContent(child);
297       return value;
298     }
299
300     public static Element addChildWithName(Element parent, String name, Object value) {
301       Element child = new Element(OPTION);
302       child.setAttribute(NAME, name);
303       child.setAttribute(VALUE, value.toString());
304       parent.addContent(child);
305       return child;
306     }
307
308     public static Element addChildList(Element parent, String name, List<Element> elements) {
309       Element listElement = new Element(LIST);
310       for (Element element : elements) {
311         listElement.addContent(element);
312       }
313       return addChildWithName(parent, name, listElement);
314     }
315
316     public static Element addChildMap(Element parent, String name, Map<String, Element> value) {
317       Element mapElement = new Element(MAP);
318       for (Map.Entry<String, Element> entry : value.entrySet()) {
319         Element entryElement = new Element("entry");
320         mapElement.addContent(entryElement);
321         String key = entry.getKey();
322         entryElement.setAttribute("key", key);
323         Element valueElement = new Element("value");
324         valueElement.addContent(entry.getValue());
325         entryElement.addContent(valueElement);
326       }
327       return addChildWithName(parent, name, mapElement);
328     }
329
330     public static List<Element> getChildList(Element parent, String name) throws StudyUnrecognizedFormatException {
331       return getChildList(parent, name, false);
332     }
333
334     public static List<Element> getChildList(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
335       Element listParent = getChildWithName(parent, name, optional);
336       if (listParent != null) {
337         Element list = listParent.getChild(LIST);
338         if (list != null) {
339           return list.getChildren();
340         }
341       }
342       return Collections.emptyList();
343     }
344
345     public static Element getChildWithName(Element parent, String name) throws StudyUnrecognizedFormatException {
346       return getChildWithName(parent, name, false);
347     }
348
349     public static Element getChildWithName(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
350       for (Element child : parent.getChildren()) {
351         Attribute attribute = child.getAttribute(NAME);
352         if (attribute == null) {
353           continue;
354         }
355         if (name.equals(attribute.getValue())) {
356           return child;
357         }
358       }
359       if (optional) {
360         return null;
361       }
362       throw new StudyUnrecognizedFormatException();
363     }
364
365     public static <K, V> Map<K, V> getChildMap(Element element, String name) throws StudyUnrecognizedFormatException {
366       return getChildMap(element, name, false);
367     }
368
369     public static <K, V> Map<K, V> getChildMap(Element element, String name, boolean optional) throws StudyUnrecognizedFormatException {
370       Element mapParent = getChildWithName(element, name, optional);
371       if (mapParent != null) {
372         Element map = mapParent.getChild(MAP);
373         if (map != null) {
374           HashMap result = new HashMap();
375           for (Element entry : map.getChildren()) {
376             Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY);
377             Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE);
378             result.put(key, value);
379           }
380           return result;
381         }
382       }
383       return Collections.emptyMap();
384     }
385   }
386
387   public static class Json {
388
389     public static final String TASK_LIST = "task_list";
390     public static final String TASK_FILES = "task_files";
391     public static final String FILES = "files";
392     public static final String HINTS = "hints";
393     public static final String SUBTASK_INFOS = "subtask_infos";
394     public static final String FORMAT_VERSION = "format_version";
395     public static final String INDEX = "index";
396
397     private Json() {
398     }
399
400     public static class CourseTypeAdapter implements JsonDeserializer<Course> {
401
402       private final File myCourseFile;
403
404       public CourseTypeAdapter(File courseFile) {
405         myCourseFile = courseFile;
406       }
407
408       @Override
409       public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
410         JsonObject courseObject = json.getAsJsonObject();
411         JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
412         for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
413           JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
414           JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
415           for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
416             JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
417             for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
418               String name = taskFile.getKey();
419               String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
420               VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
421               if (resourceFile == null) {
422                 continue;
423               }
424               Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
425               if (document == null) {
426                 continue;
427               }
428               JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
429               JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
430               for (JsonElement placeholder : placeholders) {
431                 convertToAbsoluteOffset(document, placeholder);
432                 convertToSubtaskInfo(placeholder.getAsJsonObject());
433               }
434             }
435           }
436         }
437         return new GsonBuilder().create().fromJson(json, Course.class);
438       }
439
440       private static void convertToAbsoluteOffset(Document document, JsonElement placeholder) {
441         JsonObject placeholderObject = placeholder.getAsJsonObject();
442         if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
443           return;
444         }
445         int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
446         int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
447         int offset = document.getLineStartOffset(line) + start;
448         placeholderObject.addProperty(OFFSET, offset);
449       }
450     }
451
452     public static class StepicStepOptionsAdapter implements JsonDeserializer<StepicWrappers.StepOptions> {
453       @Override
454       public StepicWrappers.StepOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
455         throws JsonParseException {
456         JsonObject stepOptionsJson = json.getAsJsonObject();
457         JsonPrimitive versionJson = stepOptionsJson.getAsJsonPrimitive(FORMAT_VERSION);
458         int version = 1;
459         if (versionJson != null) {
460           version = versionJson.getAsInt();
461         }
462         switch (version) {
463           case 1:
464             stepOptionsJson = convertToSecondVersion(stepOptionsJson);
465             // uncomment for future versions
466             //case 2:
467             //  stepOptionsJson = convertToThirdVersion(stepOptionsJson);
468         }
469         convertSubtaskInfosToMap(stepOptionsJson);
470         StepicWrappers.StepOptions stepOptions =
471           new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
472             .fromJson(stepOptionsJson, StepicWrappers.StepOptions.class);
473         stepOptions.formatVersion = EduStepicConnector.CURRENT_VERSION;
474         return stepOptions;
475       }
476
477       private static JsonObject convertSubtaskInfosToMap(JsonObject stepOptionsJson) {
478         for (JsonElement taskFileElement : stepOptionsJson.getAsJsonArray(FILES)) {
479           JsonObject taskFileObject = taskFileElement.getAsJsonObject();
480           JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
481           for (JsonElement placeholder : placeholders) {
482             JsonObject placeholderObject = placeholder.getAsJsonObject();
483             JsonArray infos = placeholderObject.getAsJsonArray(SUBTASK_INFOS);
484             Map<Integer, JsonObject> objectsToInsert = new HashMap<>();
485             for (JsonElement info : infos) {
486               JsonObject object = info.getAsJsonObject();
487               int index = object.getAsJsonPrimitive(INDEX).getAsInt();
488               objectsToInsert.put(index, object);
489             }
490             placeholderObject.remove(SUBTASK_INFOS);
491             JsonObject newInfos = new JsonObject();
492             placeholderObject.add(SUBTASK_INFOS, newInfos);
493             for (Map.Entry<Integer, JsonObject> entry : objectsToInsert.entrySet()) {
494               newInfos.add(entry.getKey().toString(), entry.getValue());
495             }
496           }
497         }
498         return stepOptionsJson;
499       }
500
501       private static JsonObject convertToSecondVersion(JsonObject stepOptionsJson) {
502         Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
503         for (JsonElement taskFileElement : stepOptionsJson.getAsJsonArray(FILES)) {
504           JsonObject taskFileObject = taskFileElement.getAsJsonObject();
505           JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
506           for (JsonElement placeholder : placeholders) {
507             JsonObject placeholderObject = placeholder.getAsJsonObject();
508             convertToAbsoluteOffset(taskFileObject, placeholderObject);
509             convertMultipleHints(gson, placeholderObject);
510             convertToSubtaskInfo(placeholderObject);
511           }
512         }
513         return stepOptionsJson;
514       }
515
516       private static void convertMultipleHints(Gson gson, JsonObject placeholderObject) {
517         final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString();
518         final JsonArray hintsArray = new JsonArray();
519
520         try {
521           final Type listType = new TypeToken<List<String>>() {
522           }.getType();
523           final List<String> hints = gson.fromJson(hintString, listType);
524           if (hints != null && !hints.isEmpty()) {
525             for (int i = 0; i < hints.size(); i++) {
526               if (i == 0) {
527                 placeholderObject.addProperty(HINT, hints.get(0));
528                 continue;
529               }
530               hintsArray.add(hints.get(i));
531             }
532             placeholderObject.add(ADDITIONAL_HINTS, hintsArray);
533           }
534           else {
535             placeholderObject.addProperty(HINT, "");
536           }
537         }
538         catch (JsonParseException e) {
539           hintsArray.add(hintString);
540         }
541       }
542
543       private static void convertToAbsoluteOffset(JsonObject taskFileObject, JsonObject placeholderObject) {
544         int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
545         int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
546         if (line == -1) {
547           placeholderObject.addProperty(OFFSET, start);
548         }
549         else {
550           Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString());
551           placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start);
552         }
553       }
554     }
555
556     private static void convertToSubtaskInfo(JsonObject placeholderObject) {
557       JsonArray subtaskInfos = new JsonArray();
558       placeholderObject.add(SUBTASK_INFOS, subtaskInfos);
559       JsonArray hintsArray = new JsonArray();
560       hintsArray.add(placeholderObject.getAsJsonPrimitive(HINT).getAsString());
561       JsonArray additionalHints = placeholderObject.getAsJsonArray(ADDITIONAL_HINTS);
562       if (additionalHints != null) {
563         hintsArray.addAll(additionalHints);
564       }
565       JsonObject subtaskInfo = new JsonObject();
566       subtaskInfos.add(subtaskInfo);
567       subtaskInfo.add(INDEX, new JsonPrimitive(0));
568       subtaskInfo.add(HINTS, hintsArray);
569       subtaskInfo.addProperty(POSSIBLE_ANSWER, placeholderObject.getAsJsonPrimitive(POSSIBLE_ANSWER).getAsString());
570     }
571
572     public static class StepicAnswerPlaceholderAdapter implements JsonSerializer<AnswerPlaceholder> {
573       @Override
574       public JsonElement serialize(AnswerPlaceholder placeholder, Type typeOfSrc, JsonSerializationContext context) {
575         Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
576         JsonElement answerPlaceholderJson = gson.toJsonTree(placeholder);
577         JsonObject answerPlaceholderObject = answerPlaceholderJson.getAsJsonObject();
578         JsonObject subtaskInfos = answerPlaceholderObject.getAsJsonObject(SUBTASK_INFOS);
579         JsonArray infosArray = new JsonArray();
580         for (Map.Entry<String, JsonElement> entry : subtaskInfos.entrySet()) {
581           JsonObject subtaskInfo = entry.getValue().getAsJsonObject();
582           subtaskInfo.add(INDEX, new JsonPrimitive(Integer.valueOf(entry.getKey())));
583           infosArray.add(subtaskInfo);
584         }
585         answerPlaceholderObject.remove(SUBTASK_INFOS);
586         answerPlaceholderObject.add(SUBTASK_INFOS, infosArray);
587         return answerPlaceholderJson;
588       }
589     }
590   }
591 }