1 package com.jetbrains.edu.learning;
3 import com.google.gson.*;
4 import com.intellij.openapi.editor.Document;
5 import com.intellij.openapi.editor.EditorFactory;
6 import com.intellij.openapi.fileEditor.FileDocumentManager;
7 import com.intellij.openapi.project.Project;
8 import com.intellij.openapi.util.io.FileUtil;
9 import com.intellij.openapi.vfs.LocalFileSystem;
10 import com.intellij.openapi.vfs.VirtualFile;
11 import com.intellij.util.containers.hash.HashMap;
12 import com.jetbrains.edu.learning.core.EduNames;
13 import com.jetbrains.edu.learning.courseFormat.Course;
14 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
15 import com.jetbrains.edu.learning.courseFormat.TaskFile;
16 import org.jdom.Attribute;
17 import org.jdom.Element;
18 import org.jdom.output.XMLOutputter;
19 import org.jetbrains.annotations.NotNull;
22 import java.lang.reflect.Type;
23 import java.util.Collections;
24 import java.util.List;
27 public class StudySerializationUtils {
29 public static final String PLACEHOLDERS = "placeholders";
30 public static final String LINE = "line";
31 public static final String START = "start";
32 public static final String OFFSET = "offset";
33 public static final String TEXT = "text";
34 public static final String LESSONS = "lessons";
35 public static final String COURSE = "course";
36 public static final String COURSE_TITLED = "Course";
37 public static final String STATUS = "status";
38 public static final String AUTHOR = "author";
39 public static final String AUTHORS = "authors";
40 public static final String MY_INITIAL_START = "myInitialStart";
42 private StudySerializationUtils() {
45 public static class StudyUnrecognizedFormatException extends Exception {}
47 public static class Xml {
48 public final static String COURSE_ELEMENT = "courseElement";
49 public final static String MAIN_ELEMENT = "StudyTaskManager";
50 public static final String MAP = "map";
51 public static final String KEY = "key";
52 public static final String VALUE = "value";
53 public static final String NAME = "name";
54 public static final String LIST = "list";
55 public static final String OPTION = "option";
56 public static final String INDEX = "index";
57 public static final String STUDY_STATUS_MAP = "myStudyStatusMap";
58 public static final String TASK_STATUS_MAP = "myTaskStatusMap";
59 public static final String LENGTH = "length";
60 public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders";
61 public static final String TASK_LIST = "taskList";
62 public static final String TASK_FILES = "taskFiles";
63 public static final String INITIAL_STATE = "initialState";
64 public static final String MY_INITIAL_STATE = "MyInitialState";
65 public static final String MY_LINE = "myLine";
66 public static final String MY_START = "myStart";
67 public static final String MY_LENGTH = "myLength";
68 public static final String HINTS = "hints";
69 public static final String HINT = "hint";
70 public static final String AUTHOR_TITLED = "Author";
71 public static final String FIRST_NAME = "first_name";
72 public static final String SECOND_NAME = "second_name";
73 public static final String MY_INITIAL_LINE = "myInitialLine";
74 public static final String MY_INITIAL_LENGTH = "myInitialLength";
75 public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder";
76 public static final String TASK_WINDOWS = "taskWindows";
77 public static final String RESOURCE_PATH = "resourcePath";
78 public static final String COURSE_DIRECTORY = "courseDirectory";
83 public static int getVersion(Element element) throws StudyUnrecognizedFormatException {
84 if (element.getChild(COURSE_ELEMENT) != null) {
88 final Element taskManager = element.getChild(MAIN_ELEMENT);
90 Element versionElement = getChildWithName(taskManager, "VERSION");
91 if (versionElement == null) {
95 return Integer.valueOf(versionElement.getAttributeValue(VALUE));
98 public static Element convertToSecondVersion(Element element) throws StudyUnrecognizedFormatException {
99 final Element oldCourseElement = element.getChild(COURSE_ELEMENT);
100 Element state = new Element(MAIN_ELEMENT);
102 Element course = addChildWithName(state, COURSE, oldCourseElement.clone());
103 course.setName(COURSE_TITLED);
105 Element author = getChildWithName(course, AUTHOR);
106 String authorString = author.getAttributeValue(VALUE);
107 course.removeContent(author);
109 String[] names = authorString.split(" ", 2);
110 Element authorElement = new Element(AUTHOR_TITLED);
111 addChildWithName(authorElement, FIRST_NAME, names[0]);
112 addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]);
114 addChildList(course, AUTHORS, Collections.singletonList(authorElement));
116 Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH);
117 renameElement(courseDirectoryElement, COURSE_DIRECTORY);
119 for (Element lesson : getChildList(course, LESSONS)) {
120 incrementIndex(lesson);
121 for (Element task : getChildList(lesson, TASK_LIST)) {
122 incrementIndex(task);
123 Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
124 for (Element taskFile : taskFiles.values()) {
125 renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS);
126 for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) {
127 placeholder.setName(ANSWER_PLACEHOLDER);
129 Element initialState = new Element(MY_INITIAL_STATE);
130 addChildWithName(placeholder, INITIAL_STATE, initialState);
131 addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE));
132 addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE));
133 addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE));
139 element.removeContent();
140 element.addContent(state);
144 public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter)
145 throws StudyUnrecognizedFormatException {
146 Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
147 Map<String, String> destMap = new HashMap<>();
148 for (Map.Entry<Element, String> entry : sourceMap.entrySet()) {
149 String status = entry.getValue();
150 if (status.equals(StudyStatus.Unchecked.toString())) {
153 destMap.put(outputter.outputString(entry.getKey()), status);
158 public static Element convertToThirdVersion(Element state, Project project) throws StudyUnrecognizedFormatException {
159 Element taskManagerElement = state.getChild(MAIN_ELEMENT);
160 XMLOutputter outputter = new XMLOutputter();
162 Map<String, String> placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter);
163 Map<String, String> taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter);
165 Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
166 for (Element lesson : getChildList(courseElement, LESSONS)) {
167 int lessonIndex = getAsInt(lesson, INDEX);
168 for (Element task : getChildList(lesson, TASK_LIST)) {
169 String taskStatus = null;
170 int taskIndex = getAsInt(task, INDEX);
171 Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
172 for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
173 Element taskFileElement = entry.getValue();
174 String taskFileText = outputter.outputString(taskFileElement);
175 String taskFileStatus = taskFileToStatusMap.get(taskFileText);
176 if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) {
177 taskStatus = taskFileStatus;
179 Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey());
180 if (document == null) {
183 for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
184 taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder);
185 addOffset(document, placeholder);
186 addInitialState(document, placeholder);
187 addHints(placeholder);
190 if (taskStatus != null) {
191 addChildWithName(task, STATUS, taskStatus);
198 public static String addStatus(XMLOutputter outputter,
199 Map<String, String> placeholderTextToStatus,
201 Element placeholder) {
202 String placeholderText = outputter.outputString(placeholder);
203 String status = placeholderTextToStatus.get(placeholderText);
204 if (status != null) {
205 addChildWithName(placeholder, STATUS, status);
206 if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) {
213 public static void addInitialState(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
214 Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE);
215 int initialLine = getAsInt(initialState, MY_LINE);
216 int initialStart = getAsInt(initialState, MY_START);
217 int initialOffset = document.getLineStartOffset(initialLine) + initialStart;
218 addChildWithName(initialState, OFFSET, initialOffset);
219 renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH);
222 public static void addOffset(Document document, Element placeholder) throws StudyUnrecognizedFormatException {
223 int line = getAsInt(placeholder, LINE);
224 int start = getAsInt(placeholder, START);
225 int offset = document.getLineStartOffset(line) + start;
226 addChildWithName(placeholder, OFFSET, offset);
229 public static void addHints(@NotNull Element placeholder) throws StudyUnrecognizedFormatException {
230 final String hint = getChildWithName(placeholder, HINT).getAttribute(VALUE).getValue();
231 Element listElement = new Element(LIST);
232 final Element hintElement = new Element(OPTION);
233 hintElement.setAttribute(VALUE, hint);
234 listElement.setContent(hintElement);
235 addChildWithName(placeholder, HINTS, listElement);
238 public static int getAsInt(Element element, String name) throws StudyUnrecognizedFormatException {
239 return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE));
242 public static void incrementIndex(Element element) throws StudyUnrecognizedFormatException {
243 Element index = getChildWithName(element, INDEX);
244 int indexValue = Integer.parseInt(index.getAttributeValue(VALUE));
245 changeValue(index, indexValue + 1);
248 public static void renameElement(Element element, String newName) {
249 element.setAttribute(NAME, newName);
252 public static void changeValue(Element element, Object newValue) {
253 element.setAttribute(VALUE, newValue.toString());
256 public static Element addChildWithName(Element parent, String name, Element value) {
257 Element child = new Element(OPTION);
258 child.setAttribute(NAME, name);
259 child.addContent(value);
260 parent.addContent(child);
264 public static Element addChildWithName(Element parent, String name, Object value) {
265 Element child = new Element(OPTION);
266 child.setAttribute(NAME, name);
267 child.setAttribute(VALUE, value.toString());
268 parent.addContent(child);
272 public static Element addChildList(Element parent, String name, List<Element> elements) {
273 Element listElement = new Element(LIST);
274 for (Element element : elements) {
275 listElement.addContent(element);
277 return addChildWithName(parent, name, listElement);
280 public static List<Element> getChildList(Element parent, String name) throws StudyUnrecognizedFormatException {
281 Element listParent = getChildWithName(parent, name);
282 if (listParent != null) {
283 Element list = listParent.getChild(LIST);
285 return list.getChildren();
288 return Collections.emptyList();
291 public static Element getChildWithName(Element parent, String name) throws StudyUnrecognizedFormatException {
292 for (Element child : parent.getChildren()) {
293 Attribute attribute = child.getAttribute(NAME);
294 if (attribute == null) {
297 if (name.equals(attribute.getValue())) {
301 throw new StudyUnrecognizedFormatException();
304 public static <K, V> Map<K, V> getChildMap(Element element, String name) throws StudyUnrecognizedFormatException {
305 Element mapParent = getChildWithName(element, name);
306 if (mapParent != null) {
307 Element map = mapParent.getChild(MAP);
309 HashMap result = new HashMap();
310 for (Element entry : map.getChildren()) {
311 Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY);
312 Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE);
313 result.put(key, value);
318 return Collections.emptyMap();
322 public static class Json {
324 public static final String TASK_LIST = "task_list";
325 public static final String TASK_FILES = "task_files";
330 public static class CourseTypeAdapter implements JsonDeserializer<Course> {
332 private final File myCourseFile;
334 public CourseTypeAdapter(File courseFile) {
335 myCourseFile = courseFile;
339 public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
340 JsonObject courseObject = json.getAsJsonObject();
341 JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
342 for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
343 JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
344 JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
345 for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
346 JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
347 for (Map.Entry<String, JsonElement> taskFile : taskObject.getAsJsonObject(TASK_FILES).entrySet()) {
348 String name = taskFile.getKey();
349 String filePath = FileUtil.join(myCourseFile.getParent(), EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex, name);
350 VirtualFile resourceFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath));
351 if (resourceFile == null) {
354 Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
355 if (document == null) {
358 JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
359 JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
360 for (JsonElement placeholder : placeholders) {
361 JsonObject placeholderObject = placeholder.getAsJsonObject();
362 if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
365 int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
366 int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
367 int offset = document.getLineStartOffset(line) + start;
368 placeholderObject.addProperty(OFFSET, offset);
373 return new GsonBuilder().create().fromJson(json, Course.class);
377 public static class StepicTaskFileAdapter implements JsonDeserializer<TaskFile> {
380 public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
381 JsonObject taskFileObject = json.getAsJsonObject();
382 JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
383 for (JsonElement placeholder : placeholders) {
384 JsonObject placeholderObject = placeholder.getAsJsonObject();
385 int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
386 int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
388 placeholderObject.addProperty(OFFSET, start);
390 Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString());
391 placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start);
394 return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().fromJson(json, TaskFile.class);