1 package com.jetbrains.edu.learning;
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.courseFormat.TaskFile;
19 import org.jdom.Attribute;
20 import org.jdom.Element;
21 import org.jdom.output.XMLOutputter;
24 import java.lang.reflect.Type;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
30 public class StudySerializationUtils {
32 public static final String PLACEHOLDERS = "placeholders";
33 public static final String LINE = "line";
34 public static final String START = "start";
35 public static final String LENGTH = "length";
36 public static final String POSSIBLE_ANSWER = "possible_answer";
37 public static final String HINT = "hint";
38 public static final String ADDITIONAL_HINTS = "additional_hints";
39 public static final String OFFSET = "offset";
40 public static final String TEXT = "text";
41 public static final String LESSONS = "lessons";
42 public static final String COURSE = "course";
43 public static final String COURSE_TITLED = "Course";
44 public static final String STATUS = "status";
45 public static final String AUTHOR = "author";
46 public static final String AUTHORS = "authors";
47 public static final String MY_INITIAL_START = "myInitialStart";
49 private StudySerializationUtils() {
52 public static class StudyUnrecognizedFormatException extends Exception {
55 public static class Xml {
56 public final static String COURSE_ELEMENT = "courseElement";
57 public final static String MAIN_ELEMENT = "StudyTaskManager";
58 public static final String MAP = "map";
59 public static final String KEY = "key";
60 public static final String VALUE = "value";
61 public static final String NAME = "name";
62 public static final String LIST = "list";
63 public static final String OPTION = "option";
64 public static final String INDEX = "index";
65 public static final String STUDY_STATUS_MAP = "myStudyStatusMap";
66 public static final String TASK_STATUS_MAP = "myTaskStatusMap";
67 public static final String LENGTH = "length";
68 public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders";
69 public static final String TASK_LIST = "taskList";
70 public static final String TASK_FILES = "taskFiles";
71 public static final String INITIAL_STATE = "initialState";
72 public static final String MY_INITIAL_STATE = "MyInitialState";
73 public static final String MY_LINE = "myLine";
74 public static final String MY_START = "myStart";
75 public static final String MY_LENGTH = "myLength";
76 public static final String HINT = "hint";
77 public static final String AUTHOR_TITLED = "Author";
78 public static final String FIRST_NAME = "first_name";
79 public static final String SECOND_NAME = "second_name";
80 public static final String MY_INITIAL_LINE = "myInitialLine";
81 public static final String MY_INITIAL_LENGTH = "myInitialLength";
82 public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder";
83 public static final String TASK_WINDOWS = "taskWindows";
84 public static final String RESOURCE_PATH = "resourcePath";
85 public static final String COURSE_DIRECTORY = "courseDirectory";
86 public static final String SUBTASK_INFO = "AnswerPlaceholderSubtaskInfo";
87 public static final String SUBTASK_INFOS = "subtaskInfos";
88 public static final String ADDITIONAL_HINTS = "additionalHints";
89 public static final String POSSIBLE_ANSWER = "possibleAnswer";
90 public static final String SELECTED = "selected";
91 public static final String TASK_TEXT = "taskText";
92 public static final String PLACEHOLDER_TEXT = "placeholderText";
97 public static int getVersion(Element element) throws StudyUnrecognizedFormatException {
98 if (element.getChild(COURSE_ELEMENT) != null) {
102 final Element taskManager = element.getChild(MAIN_ELEMENT);
104 Element versionElement = getChildWithName(taskManager, "VERSION");
105 if (versionElement == null) {
109 return Integer.valueOf(versionElement.getAttributeValue(VALUE));
112 public static Element convertToSecondVersion(Element element) throws StudyUnrecognizedFormatException {
113 final Element oldCourseElement = element.getChild(COURSE_ELEMENT);
114 Element state = new Element(MAIN_ELEMENT);
116 Element course = addChildWithName(state, COURSE, oldCourseElement.clone());
117 course.setName(COURSE_TITLED);
119 Element author = getChildWithName(course, AUTHOR);
120 String authorString = author.getAttributeValue(VALUE);
121 course.removeContent(author);
123 String[] names = authorString.split(" ", 2);
124 Element authorElement = new Element(AUTHOR_TITLED);
125 addChildWithName(authorElement, FIRST_NAME, names[0]);
126 addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]);
128 addChildList(course, AUTHORS, Collections.singletonList(authorElement));
130 Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH);
131 renameElement(courseDirectoryElement, COURSE_DIRECTORY);
133 for (Element lesson : getChildList(course, LESSONS)) {
134 incrementIndex(lesson);
135 for (Element task : getChildList(lesson, TASK_LIST)) {
136 incrementIndex(task);
137 Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
138 for (Element taskFile : taskFiles.values()) {
139 renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS);
140 for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) {
141 placeholder.setName(ANSWER_PLACEHOLDER);
143 Element initialState = new Element(MY_INITIAL_STATE);
144 addChildWithName(placeholder, INITIAL_STATE, initialState);
145 addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE));
146 addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE));
147 addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE));
152 element.removeContent();
153 element.addContent(state);
157 public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter)
158 throws StudyUnrecognizedFormatException {
159 Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
160 Map<String, String> destMap = new HashMap<>();
161 for (Map.Entry<Element, String> entry : sourceMap.entrySet()) {
162 String status = entry.getValue();
163 if (status.equals(StudyStatus.Unchecked.toString())) {
166 destMap.put(outputter.outputString(entry.getKey()), status);
171 public static Element convertToThirdVersion(Element state, Project project) throws StudyUnrecognizedFormatException {
172 Element taskManagerElement = state.getChild(MAIN_ELEMENT);
173 XMLOutputter outputter = new XMLOutputter();
175 Map<String, String> placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter);
176 Map<String, String> taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter);
178 Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
179 for (Element lesson : getChildList(courseElement, LESSONS)) {
180 int lessonIndex = getAsInt(lesson, INDEX);
181 for (Element task : getChildList(lesson, TASK_LIST)) {
182 String taskStatus = null;
183 int taskIndex = getAsInt(task, INDEX);
184 Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
185 for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
186 Element taskFileElement = entry.getValue();
187 String taskFileText = outputter.outputString(taskFileElement);
188 String taskFileStatus = taskFileToStatusMap.get(taskFileText);
189 if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) {
190 taskStatus = taskFileStatus;
192 Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey());
193 if (document == null) {
196 for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
197 taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder);
198 addOffset(document, placeholder);
199 addInitialState(document, placeholder);
202 if (taskStatus != null) {
203 addChildWithName(task, STATUS, taskStatus);
210 public static Element convertToForthVersion(Element state) throws StudyUnrecognizedFormatException {
211 Element taskManagerElement = state.getChild(MAIN_ELEMENT);
212 Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
213 for (Element lesson : getChildList(courseElement, LESSONS)) {
214 for (Element task : getChildList(lesson, TASK_LIST)) {
215 Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
216 for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
217 Element taskFileElement = entry.getValue();
218 for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
219 Element valueElement = new Element(SUBTASK_INFO);
220 addChildMap(placeholder, SUBTASK_INFOS, Collections.singletonMap(String.valueOf(0), valueElement));
221 for (String childName : ContainerUtil
222 .list(HINT, ADDITIONAL_HINTS, POSSIBLE_ANSWER, SELECTED, STATUS, TASK_TEXT)) {
223 Element child = getChildWithName(placeholder, childName);
224 valueElement.addContent(child.clone());
226 renameElement(getChildWithName(valueElement, TASK_TEXT), PLACEHOLDER_TEXT);
227 List<Element> additionalHints = ContainerUtil.map(getChildList(valueElement, ADDITIONAL_HINTS), Element::clone);
228 Element hint = getChildWithName(valueElement, HINT);
229 Element firstHint = new Element(OPTION).setAttribute(VALUE, hint.getAttributeValue(VALUE));
230 List<Element> newHints = new ArrayList<>();
231 newHints.add(firstHint);
232 newHints.addAll(additionalHints);
233 addChildList(valueElement, "hints", newHints);
242 public static String addStatus(XMLOutputter outputter,
243 Map<String, String> placeholderTextToStatus,
245 Element placeholder) {
246 String placeholderText = outputter.outputString(placeholder);
247 String status = placeholderTextToStatus.get(placeholderText);
248 if (status != null) {
249 addChildWithName(placeholder, STATUS, status);
250 if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) {
257 public static void addInitialState(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
258 Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE);
259 int initialLine = getAsInt(initialState, MY_LINE);
260 int initialStart = getAsInt(initialState, MY_START);
261 int initialOffset = document.getLineStartOffset(initialLine) + initialStart;
262 addChildWithName(initialState, OFFSET, initialOffset);
263 renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH);
266 public static void addOffset(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
267 int line = getAsInt(placeholder, LINE);
268 int start = getAsInt(placeholder, START);
269 int offset = document.getLineStartOffset(line) + start;
270 addChildWithName(placeholder, OFFSET, offset);
273 public static int getAsInt(Element element, String name) throws StudyUnrecognizedFormatException {
274 return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE));
277 public static void incrementIndex(Element element) throws StudyUnrecognizedFormatException {
278 Element index = getChildWithName(element, INDEX);
279 int indexValue = Integer.parseInt(index.getAttributeValue(VALUE));
280 changeValue(index, indexValue + 1);
283 public static void renameElement(Element element, String newName) {
284 element.setAttribute(NAME, newName);
287 public static void changeValue(Element element, Object newValue) {
288 element.setAttribute(VALUE, newValue.toString());
291 public static Element addChildWithName(Element parent, String name, Element value) {
292 Element child = new Element(OPTION);
293 child.setAttribute(NAME, name);
294 child.addContent(value);
295 parent.addContent(child);
299 public static Element addChildWithName(Element parent, String name, Object value) {
300 Element child = new Element(OPTION);
301 child.setAttribute(NAME, name);
302 child.setAttribute(VALUE, value.toString());
303 parent.addContent(child);
307 public static Element addChildList(Element parent, String name, List<Element> elements) {
308 Element listElement = new Element(LIST);
309 for (Element element : elements) {
310 listElement.addContent(element);
312 return addChildWithName(parent, name, listElement);
315 public static Element addChildMap(Element parent, String name, Map<String, Element> value) {
316 Element mapElement = new Element(MAP);
317 for (Map.Entry<String, Element> entry : value.entrySet()) {
318 Element entryElement = new Element("entry");
319 mapElement.addContent(entryElement);
320 String key = entry.getKey();
321 entryElement.setAttribute("key", key);
322 Element valueElement = new Element("value");
323 valueElement.addContent(entry.getValue());
324 entryElement.addContent(valueElement);
326 return addChildWithName(parent, name, mapElement);
329 public static List<Element> getChildList(Element parent, String name) throws StudyUnrecognizedFormatException {
330 return getChildList(parent, name, false);
333 public static List<Element> getChildList(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
334 Element listParent = getChildWithName(parent, name, optional);
335 if (listParent != null) {
336 Element list = listParent.getChild(LIST);
338 return list.getChildren();
341 return Collections.emptyList();
344 public static Element getChildWithName(Element parent, String name) throws StudyUnrecognizedFormatException {
345 return getChildWithName(parent, name, false);
348 public static Element getChildWithName(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException {
349 for (Element child : parent.getChildren()) {
350 Attribute attribute = child.getAttribute(NAME);
351 if (attribute == null) {
354 if (name.equals(attribute.getValue())) {
361 throw new StudyUnrecognizedFormatException();
364 public static <K, V> Map<K, V> getChildMap(Element element, String name) throws StudyUnrecognizedFormatException {
365 return getChildMap(element, name, false);
368 public static <K, V> Map<K, V> getChildMap(Element element, String name, boolean optional) throws StudyUnrecognizedFormatException {
369 Element mapParent = getChildWithName(element, name, optional);
370 if (mapParent != null) {
371 Element map = mapParent.getChild(MAP);
373 HashMap result = new HashMap();
374 for (Element entry : map.getChildren()) {
375 Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY);
376 Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE);
377 result.put(key, value);
382 return Collections.emptyMap();
386 public static class Json {
388 public static final String TASK_LIST = "task_list";
389 public static final String TASK_FILES = "task_files";
394 public static class CourseTypeAdapter implements JsonDeserializer<Course> {
396 private final File myCourseFile;
398 public CourseTypeAdapter(File courseFile) {
399 myCourseFile = courseFile;
403 public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
404 JsonObject courseObject = json.getAsJsonObject();
405 JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
406 for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
407 JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
408 JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
409 for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
410 JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
411 for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
412 String name = taskFile.getKey();
413 String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
414 VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
415 if (resourceFile == null) {
418 Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
419 if (document == null) {
422 JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
423 JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
424 for (JsonElement placeholder : placeholders) {
425 JsonObject placeholderObject = placeholder.getAsJsonObject();
426 if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
429 int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
430 int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
431 int offset = document.getLineStartOffset(line) + start;
432 placeholderObject.addProperty(OFFSET, offset);
437 return new GsonBuilder().create().fromJson(json, Course.class);
441 public static class StepicTaskFileAdapter implements JsonDeserializer<TaskFile> {
444 public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
445 final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
446 JsonObject taskFileObject = json.getAsJsonObject();
447 JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
448 for (JsonElement placeholder : placeholders) {
449 JsonObject placeholderObject = placeholder.getAsJsonObject();
450 int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
451 int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
453 placeholderObject.addProperty(OFFSET, start);
456 Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString());
457 placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start);
459 final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString();
460 final JsonArray hintsArray = new JsonArray();
463 final Type listType = new TypeToken<List<String>>() {}.getType();
464 final List<String> hints = gson.fromJson(hintString, listType);
465 if (hints != null && !hints.isEmpty()) {
466 for (int i = 0; i < hints.size(); i++) {
468 placeholderObject.addProperty(HINT, hints.get(0));
471 hintsArray.add(hints.get(i));
473 placeholderObject.add(ADDITIONAL_HINTS, hintsArray);
476 placeholderObject.addProperty(HINT, "");
479 catch (JsonParseException e) {
480 hintsArray.add(hintString);
484 return gson.fromJson(json, TaskFile.class);
488 public static class StepicAnswerPlaceholderAdapter implements JsonSerializer<AnswerPlaceholder> {
490 public JsonElement serialize(AnswerPlaceholder src, Type typeOfSrc, JsonSerializationContext context) {
491 final List<String> hints = src.getHints();
493 final int length = src.getLength();
494 final int start = src.getOffset();
495 final String possibleAnswer = src.getPossibleAnswer();
498 final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
499 final JsonObject answerPlaceholder = new JsonObject();
500 answerPlaceholder.addProperty(LINE, line);
501 answerPlaceholder.addProperty(START, start);
502 answerPlaceholder.addProperty(LENGTH, length);
503 answerPlaceholder.addProperty(POSSIBLE_ANSWER, possibleAnswer);
505 final String jsonHints = gson.toJson(hints);
506 answerPlaceholder.addProperty(HINT, jsonHints);
508 return answerPlaceholder;