EDU-619 Replace line-offset placeholder representation with absolute offset
authorLiana Bakradze <liana.bakradze@jetbrains.com>
Sun, 19 Jun 2016 18:46:56 +0000 (21:46 +0300)
committerLiana Bakradze <liana.bakradze@jetbrains.com>
Sun, 19 Jun 2016 18:46:56 +0000 (21:46 +0300)
44 files changed:
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java
python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java
python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java
python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java
python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java [deleted file]
python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java
python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java
python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyProgressToolWindowFactory.java
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java

index b5a23ba917ed33b3a2e9835e9199f1a5a4e164f4..3dec53619b9fd61b8398965e02ed237fb6aacb96 100644 (file)
@@ -1,17 +1,32 @@
 package com.jetbrains.edu.coursecreator;
 
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.components.AbstractProjectComponent;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
 import com.jetbrains.edu.learning.StudyProjectComponent;
 import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import com.jetbrains.edu.learning.courseFormat.Lesson;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 public class CCProjectComponent extends AbstractProjectComponent {
+  private static final Logger LOG = Logger.getInstance(CCProjectComponent.class);
   private final CCVirtualFileListener myTaskFileLifeListener = new CCVirtualFileListener();
   private final Project myProject;
 
@@ -20,21 +35,65 @@ public class CCProjectComponent extends AbstractProjectComponent {
     myProject = project;
   }
 
-  public void initComponent() {
-    VirtualFileManager.getInstance().addVirtualFileListener(myTaskFileLifeListener);
-  }
-
   public void migrateIfNeeded() {
     Course studyCourse = StudyTaskManager.getInstance(myProject).getCourse();
-    Course course = CCProjectService.getInstance(myProject).getCourse();
-    if (studyCourse == null && course != null) {
-      course.setCourseMode(CCUtils.COURSE_MODE);
+    if (studyCourse == null) {
+      Course oldCourse = CCProjectService.getInstance(myProject).getCourse();
+      if (oldCourse == null) {
+        return;
+      }
+      StudyTaskManager.getInstance(myProject).setCourse(oldCourse);
+      CCProjectService.getInstance(myProject).setCourse(null);
+      oldCourse.initCourse(true);
+      oldCourse.setCourseMode(CCUtils.COURSE_MODE);
       File coursesDir = new File(PathManager.getConfigPath(), "courses");
-      File courseDir = new File(coursesDir, course.getName() + "-" + myProject.getName());
-      course.setCourseDirectory(courseDir.getPath());
-      StudyTaskManager.getInstance(myProject).setCourse(course);
-      StudyProjectComponent.getInstance(myProject).registerStudyToolWindow(course);
+      File courseDir = new File(coursesDir, oldCourse.getName() + "-" + myProject.getName());
+      oldCourse.setCourseDirectory(courseDir.getPath());
+      StudyProjectComponent.getInstance(myProject).registerStudyToolWindow(oldCourse);
+      transformFiles(oldCourse, myProject);
+    }
+  }
+
+  private static void transformFiles(Course course, Project project) {
+    List<VirtualFile> files = getAllAnswerTaskFiles(course, project);
+    for (VirtualFile answerFile : files) {
+      ApplicationManager.getApplication().runWriteAction(() -> {
+        String answerName = answerFile.getName();
+        String name = FileUtil.getNameWithoutExtension(FileUtil.getNameWithoutExtension(answerName)) + "." + FileUtilRt.getExtension(answerName);
+          VirtualFile file = answerFile.getParent().findChild(name);
+            try {
+              if (file != null) {
+                file.delete(CCProjectComponent.class);
+              }
+              answerFile.rename(CCProjectComponent.class, name);
+            }
+            catch (IOException e) {
+              LOG.error(e);
+            }
+      });
+    }
+  }
+
+
+  private static List<VirtualFile> getAllAnswerTaskFiles(@NotNull Course course, @NotNull Project project) {
+    List<VirtualFile> result = new ArrayList<>();
+    for (Lesson lesson : course.getLessons()) {
+      for (Task task : lesson.getTaskList()) {
+        for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+          String name = entry.getKey();
+          String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name);
+          String taskPath = FileUtil.join(project.getBasePath(), EduNames.LESSON + lesson.getIndex(), EduNames.TASK + task.getIndex());
+          VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, answerName));
+          if (taskFile == null) {
+            taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, answerName));
+          }
+          if (taskFile!= null) {
+            result.add(taskFile);
+          }
+        }
+      }
     }
+    return result;
   }
 
   @NotNull
index 2ff6f8051033d6c55ef704697f2cc0fda3a2f349..44ed180d0901186aa810ad171cad9880125bdf40 100644 (file)
@@ -19,14 +19,37 @@ import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.components.State;
 import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.project.Project;
-import com.intellij.util.xmlb.XmlSerializerUtil;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.util.xmlb.XmlSerializer;
+import com.intellij.util.xmlb.annotations.Transient;
+import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.courseFormat.Course;
+import org.jdom.Element;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Map;
+
+import static com.jetbrains.edu.learning.StudySerializationUtils.*;
+import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.*;
+
+/**
+ * @deprecated since version 3
+ */
 @State(name = "CCProjectService", storages = @Storage("course_service.xml"))
-public class CCProjectService implements PersistentStateComponent<CCProjectService> {
+public class CCProjectService implements PersistentStateComponent<Element> {
   private Course myCourse;
+  @Transient private final Project myProject;
+
+  public CCProjectService() {
+    this(null);
+  }
+
+  public CCProjectService(Project project) {
+    myProject = project;
+  }
 
   public Course getCourse() {
     return myCourse;
@@ -37,14 +60,39 @@ public class CCProjectService implements PersistentStateComponent<CCProjectServi
   }
 
   @Override
-  public CCProjectService getState() {
-    return this;
+  public Element getState() {
+    return XmlSerializer.serialize(this);
   }
 
   @Override
-  public void loadState(CCProjectService state) {
-    XmlSerializerUtil.copyBean(state, this);
-    myCourse.initCourse(true);
+  public void loadState(Element state) {
+    Element courseElement = getChildWithName(state, COURSE).getChild(COURSE_TITLED);
+    for (Element lesson : getChildList(courseElement, LESSONS)) {
+      int lessonIndex = getAsInt(lesson, INDEX);
+      for (Element task : getChildList(lesson, TASK_LIST)) {
+        int taskIndex = getAsInt(task, INDEX);
+        Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
+        for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
+          Element taskFileElement = entry.getValue();
+          String name = entry.getKey();
+          String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name);
+          Document document = StudyUtils.getDocument(myProject.getBasePath(), lessonIndex, taskIndex, answerName);
+          if (document == null) {
+            continue;
+          }
+          for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
+            Element lineElement = getChildWithName(placeholder, LINE);
+            int line = lineElement != null ? Integer.valueOf(lineElement.getAttributeValue(VALUE)) : 0;
+            Element startElement = getChildWithName(placeholder, START);
+            int start = startElement != null ? Integer.valueOf(startElement.getAttributeValue(VALUE)) : 0;
+            int offset = document.getLineStartOffset(line) + start;
+            addChildWithName(placeholder, OFFSET, offset);
+            addChildWithName(placeholder, "useLength", "false");
+          }
+        }
+      }
+    }
+    XmlSerializer.deserializeInto(this, state);
   }
 
   public static CCProjectService getInstance(@NotNull Project project) {
index e1504ce29eded44793f14f3935cc109001691c22..5ab441fd84bfd27935e19544f5a6d013e8f1d76c 100644 (file)
@@ -34,6 +34,7 @@ import java.io.IOException;
 import java.util.*;
 
 public class CCUtils {
+  public static final String ANSWER_EXTENSION_DOTTED = ".answer.";
   private static final Logger LOG = Logger.getInstance(CCUtils.class);
   public static final String GENERATED_FILES_FOLDER = ".coursecreator";
   public static final String COURSE_MODE = "Course Creator";
index 5f5922edf027cb24b704e8c0c43d008a7c49f853..36b36d02e09674a524569fadc31db11aed8e181f 100644 (file)
@@ -1,6 +1,5 @@
 package com.jetbrains.edu.coursecreator;
 
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectUtil;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -17,8 +16,6 @@ import org.jetbrains.annotations.NotNull;
 
 public class CCVirtualFileListener extends VirtualFileAdapter {
 
-  private static final Logger LOG = Logger.getInstance(CCVirtualFileListener.class);
-
   @Override
   public void fileCreated(@NotNull VirtualFileEvent event) {
     VirtualFile createdFile = event.getFile();
index 254d64dd534cd1499af9b47e8f3674d020b02d67..41275862f6f099fbc2ffb5096c03961e197e419f 100644 (file)
@@ -30,10 +30,10 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
   }
 
 
-  private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, @NotNull final Document document, int start, int end) {
+  private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) {
     List<AnswerPlaceholder> answerPlaceholders = taskFile.getAnswerPlaceholders();
     for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) {
-      int twStart = existingAnswerPlaceholder.getRealStartOffset(document);
+      int twStart = existingAnswerPlaceholder.getOffset();
       int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart;
       if ((start >= twStart && start < twEnd) || (end > twStart && end <= twEnd) ||
           (twStart >= start && twStart < end) || (twEnd > start && twEnd <= end)) {
@@ -54,13 +54,12 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
     }
 
     final SelectionModel model = editor.getSelectionModel();
-    final int start = model.getSelectionStart();
-    final int lineNumber = document.getLineNumber(start);
-    int realStart = start - document.getLineStartOffset(lineNumber);
+    final int offset = model.getSelectionStart();
     final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder();
-    answerPlaceholder.setLine(lineNumber);
-    answerPlaceholder.setStart(realStart);
+
+    answerPlaceholder.setOffset(offset);
     answerPlaceholder.setUseLength(false);
+
     String selectedText = model.getSelectedText();
     answerPlaceholder.setPossibleAnswer(selectedText);
 
@@ -103,7 +102,8 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
 
     for (int i = 0; i < placeholders.size(); i++) {
       AnswerPlaceholder fromPlaceholder = placeholders.get(i);
-      taskFile.getAnswerPlaceholders().get(i).setInitialState(fromPlaceholder);
+      AnswerPlaceholder.MyInitialState state = fromPlaceholder.getInitialState();
+      taskFile.getAnswerPlaceholders().get(i).setInitialState(new AnswerPlaceholder.MyInitialState(state.getOffset(), state.getLength()));
     }
   }
 
@@ -161,7 +161,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction {
     }
     int start = selectionModel.getSelectionStart();
     int end = selectionModel.getSelectionEnd();
-    return !arePlaceholdersIntersect(state.getTaskFile(), editor.getDocument(), start, end);
+    return !arePlaceholdersIntersect(state.getTaskFile(), start, end);
   }
 
   private static boolean canDeletePlaceholder(@NotNull CCState state) {
index e1633044d5e6ea34c1ba82900af38739bde6b054..4a04794030947afda716c3c1053be71a62939a58 100644 (file)
@@ -44,9 +44,9 @@ abstract public class CCAnswerPlaceholderAction extends DumbAwareAction {
     if (taskFile == null) {
       return null;
     }
-    AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(editor.getDocument(),
-                                                                        editor.getCaretModel().getLogicalPosition(),
-                                                                        true);
+    AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(
+      editor.getCaretModel().getOffset()
+    );
     return new CCState(taskFile, answerPlaceholder, psiFile, editor, project);
   }
 
index 40a81611150e71e5a81f2787b997455b456281de..ccfe1298a4a00b356dc0492374786cdae39780ce 100644 (file)
@@ -17,18 +17,17 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
 import com.intellij.platform.templates.github.ZipUtil;
 import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.learning.StudySerializationUtils;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.core.EduDocumentListener;
 import com.jetbrains.edu.learning.core.EduNames;
-import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.*;
-import com.jetbrains.edu.learning.oldCourseFormat.OldCourse;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.*;
@@ -62,19 +61,17 @@ public class CCFromCourseArchive extends DumbAwareAction {
     Reader reader = null;
     try {
       ZipUtil.unzip(null, new File(basePath), new File(virtualFile.getPath()), null, null, true);
-      reader = new InputStreamReader(new FileInputStream(new File(basePath, EduNames.COURSE_META_FILE)));
-      Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+      File courseMetaFile = new File(basePath, EduNames.COURSE_META_FILE);
+      reader = new InputStreamReader(new FileInputStream(courseMetaFile));
+      Gson gson = new GsonBuilder()
+        .registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseMetaFile))
+        .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+        .create();
       Course course = gson.fromJson(reader, Course.class);
-      if (course == null || course.getLessons().isEmpty() || StringUtil.isEmptyOrSpaces(course.getLessons().get(0).getName())) {
-        try {
-          reader.close();
-        }
-        catch (IOException e) {
-          LOG.error(e.getMessage());
-        }
-        reader = new InputStreamReader(new FileInputStream(new File(basePath, EduNames.COURSE_META_FILE)));
-        OldCourse oldCourse = gson.fromJson(reader, OldCourse.class);
-        course = EduUtils.transformOldCourse(oldCourse);
+
+      if (course == null) {
+        Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course");
+        return;
       }
 
       StudyTaskManager.getInstance(project).setCourse(course);
@@ -152,7 +149,7 @@ public class CCFromCourseArchive extends DumbAwareAction {
   private static void replaceAnswerPlaceholder(@NotNull final Project project,
                                                @NotNull final Document document,
                                                @NotNull final AnswerPlaceholder answerPlaceholder) {
-    final int offset = answerPlaceholder.getRealStartOffset(document);
+    final int offset = answerPlaceholder.getOffset();
     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
       final String text = document.getText(TextRange.create(offset, offset + answerPlaceholder.getRealLength()));
       answerPlaceholder.setTaskText(text);
index ad364dfc02f293ddf32c7d7fe69d661190a12ab5..210a8e766f7e1cab41d56cf0bb28dc3a150b1ccd 100644 (file)
@@ -37,8 +37,7 @@ public class StudyAnswerPlaceholderExtendWordHandler implements ExtendWordSelect
       return null;
     }
     Editor editor = FileEditorManager.getInstance(e.getProject()).getSelectedTextEditor();
-    return editor == null ? null : taskFile.getAnswerPlaceholder(document,
-                                          editor.offsetToLogicalPosition(offset));
+    return editor == null ? null : taskFile.getAnswerPlaceholder(offset);
   }
 
 
@@ -55,10 +54,7 @@ public class StudyAnswerPlaceholderExtendWordHandler implements ExtendWordSelect
   public List<TextRange> select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) {
     AnswerPlaceholder placeholder = getAnswerPlaceholder(e, cursorOffset);
     assert placeholder != null;
-    VirtualFile file = e.getContainingFile().getVirtualFile();
-    Document document = FileDocumentManager.getInstance().getDocument(file);
-    assert document != null;
-    int startOffset = placeholder.getRealStartOffset(document);
+    int startOffset = placeholder.getOffset();
     return Collections.singletonList(new TextRange(startOffset, startOffset + placeholder.getRealLength()));
   }
 }
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java
new file mode 100644 (file)
index 0000000..83b00b1
--- /dev/null
@@ -0,0 +1,385 @@
+package com.jetbrains.edu.learning;
+
+import com.google.gson.*;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+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.hash.HashMap;
+import com.jetbrains.edu.learning.core.EduNames;
+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;
+
+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 OFFSET = "offset";
+  public static final String TEXT = "text";
+  public static final String LESSONS = "lessons";
+  public static final String COURSE = "course";
+  public static final String COURSE_TITLED = "Course";
+  public static final String STATUS = "status";
+  public static final String AUTHOR = "author";
+  public static final String AUTHORS = "authors";
+  public static final String MY_INITIAL_START = "myInitialStart";
+
+  private StudySerializationUtils() {
+  }
+
+  public static class Xml {
+    public final static String COURSE_ELEMENT = "courseElement";
+    public final static String MAIN_ELEMENT = "StudyTaskManager";
+    public static final String MAP = "map";
+    public static final String KEY = "key";
+    public static final String VALUE = "value";
+    public static final String NAME = "name";
+    public static final String LIST = "list";
+    public static final String OPTION = "option";
+    public static final String INDEX = "index";
+    public static final String STUDY_STATUS_MAP = "myStudyStatusMap";
+    public static final String TASK_STATUS_MAP = "myTaskStatusMap";
+    public static final String LENGTH = "length";
+    public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders";
+    public static final String TASK_LIST = "taskList";
+    public static final String TASK_FILES = "taskFiles";
+    public static final String INITIAL_STATE = "initialState";
+    public static final String MY_INITIAL_STATE = "MyInitialState";
+    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 AUTHOR_TITLED = "Author";
+    public static final String FIRST_NAME = "first_name";
+    public static final String SECOND_NAME = "second_name";
+    public static final String MY_INITIAL_LINE = "myInitialLine";
+    public static final String MY_INITIAL_LENGTH = "myInitialLength";
+    public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder";
+    public static final String TASK_WINDOWS = "taskWindows";
+    public static final String RESOURCE_PATH = "resourcePath";
+    public static final String COURSE_DIRECTORY = "courseDirectory";
+
+    private Xml() {
+    }
+
+    public static int getVersion(Element element) {
+      if (element.getChild(COURSE_ELEMENT) != null) {
+        return 1;
+      }
+
+      final Element taskManager = element.getChild(MAIN_ELEMENT);
+
+      Element versionElement = getChildWithName(taskManager, "VERSION");
+      if (versionElement == null) {
+        return -1;
+      }
+
+      return Integer.valueOf(versionElement.getAttributeValue("value"));
+    }
+
+    public static Element convertToSecondVersion(Element element) {
+      final Element oldCourseElement = element.getChild(COURSE_ELEMENT);
+      Element state = new Element(MAIN_ELEMENT);
+
+      Element course = addChildWithName(state, COURSE, oldCourseElement.clone());
+      course.setName(COURSE_TITLED);
+
+      Element author = getChildWithName(course, AUTHOR);
+      String authorString = author.getAttributeValue(VALUE);
+      course.removeContent(author);
+
+      String[] names = authorString.split(" ", 2);
+      Element authorElement = new Element(AUTHOR_TITLED);
+      addChildWithName(authorElement, FIRST_NAME, names[0]);
+      addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]);
+
+      addChildList(course, AUTHORS, Arrays.asList(authorElement));
+
+      Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH);
+      renameElement(courseDirectoryElement, COURSE_DIRECTORY);
+
+      for (Element lesson : getChildList(course, LESSONS)) {
+        incrementIndex(lesson);
+        for (Element task : getChildList(lesson, TASK_LIST)) {
+          incrementIndex(task);
+          Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
+          for (Element taskFile : taskFiles.values()) {
+            renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS);
+            for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) {
+              placeholder.setName(ANSWER_PLACEHOLDER);
+
+              Element initialState = new Element(MY_INITIAL_STATE);
+              addChildWithName(placeholder, INITIAL_STATE, initialState);
+              addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE));
+              addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE));
+              addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE));
+            }
+          }
+
+        }
+      }
+      element.removeContent();
+      element.addContent(state);
+      return element;
+    }
+
+    public static Map<String, String> fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter) {
+      Map<Element, String> sourceMap = getChildMap(taskManagerElement, mapName);
+      Map<String, String> destMap = new HashMap<>();
+      for (Map.Entry<Element, String> entry : sourceMap.entrySet()) {
+        String status = entry.getValue();
+        if (status.equals(StudyStatus.Unchecked.toString())) {
+          continue;
+        }
+        destMap.put(outputter.outputString(entry.getKey()), status);
+      }
+      return destMap;
+    }
+
+    public static Element convertToThirdVersion(Element state, Project project) {
+      Element taskManagerElement = state.getChild(MAIN_ELEMENT);
+      XMLOutputter outputter = new XMLOutputter();
+
+      Map<String, String> placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter);
+      Map<String, String> taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter);
+
+      Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED);
+      for (Element lesson : getChildList(courseElement, LESSONS)) {
+        int lessonIndex = getAsInt(lesson, INDEX);
+        for (Element task : getChildList(lesson, TASK_LIST)) {
+          String taskStatus = null;
+          int taskIndex = getAsInt(task, INDEX);
+          Map<String, Element> taskFiles = getChildMap(task, TASK_FILES);
+          for (Map.Entry<String, Element> entry : taskFiles.entrySet()) {
+            Element taskFileElement = entry.getValue();
+            String taskFileText = outputter.outputString(taskFileElement);
+            String taskFileStatus = taskFileToStatusMap.get(taskFileText);
+            if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) {
+              taskStatus = taskFileStatus;
+            }
+            Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey());
+            if (document == null) {
+              continue;
+            }
+            for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) {
+              taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder);
+              addOffset(document, placeholder);
+              addInitialState(document, placeholder);
+            }
+          }
+          if (taskStatus != null) {
+            addChildWithName(task, STATUS, taskStatus);
+          }
+        }
+      }
+      return state;
+    }
+
+    public static String addStatus(XMLOutputter outputter,
+                                   Map<String, String> placeholderTextToStatus,
+                                   String taskStatus,
+                                   Element placeholder) {
+      String placeholderText = outputter.outputString(placeholder);
+      String status = placeholderTextToStatus.get(placeholderText);
+      if (status != null) {
+        addChildWithName(placeholder, STATUS, status);
+        if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) {
+          taskStatus = status;
+        }
+      }
+      return taskStatus;
+    }
+
+    public static void addInitialState(Document document, Element placeholder) {
+      Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE);
+      int initialLine = getAsInt(initialState, MY_LINE);
+      int initialStart = getAsInt(initialState, MY_START);
+      int initialOffset = document.getLineStartOffset(initialLine) + initialStart;
+      addChildWithName(initialState, OFFSET, initialOffset);
+      renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH);
+    }
+
+    public static void addOffset(Document document, Element placeholder) {
+      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) {
+      return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE));
+    }
+
+    public static void incrementIndex(Element element) {
+      Element index = getChildWithName(element, INDEX);
+      int indexValue = Integer.parseInt(index.getAttributeValue(VALUE));
+      changeValue(index, indexValue + 1);
+    }
+
+    public static void renameElement(Element element, String newName) {
+      element.setAttribute(NAME, newName);
+    }
+
+    public static void changeValue(Element element, Object newValue) {
+      element.setAttribute(VALUE, newValue.toString());
+    }
+
+    public static Element addChildWithName(Element parent, String name, Element value) {
+      Element child = new Element(OPTION);
+      child.setAttribute(NAME, name);
+      child.addContent(value);
+      parent.addContent(child);
+      return value;
+    }
+
+    public static Element addChildWithName(Element parent, String name, Object value) {
+      Element child = new Element(OPTION);
+      child.setAttribute(NAME, name);
+      child.setAttribute(VALUE, value.toString());
+      parent.addContent(child);
+      return child;
+    }
+
+    public static Element addChildList(Element parent, String name, List<Element> elements) {
+      Element listElement = new Element(LIST);
+      for (Element element : elements) {
+        listElement.addContent(element);
+      }
+      return addChildWithName(parent, name, listElement);
+    }
+
+    public static List<Element> getChildList(Element parent, String name) {
+      Element listParent = getChildWithName(parent, name);
+      if (listParent != null) {
+        Element list = listParent.getChild(LIST);
+        if (list != null) {
+          return list.getChildren();
+        }
+      }
+      return Collections.emptyList();
+    }
+
+    @Nullable
+    public static Element getChildWithName(Element parent, String name) {
+      for (Element child : parent.getChildren()) {
+        Attribute attribute = child.getAttribute(NAME);
+        if (attribute == null) {
+          continue;
+        }
+        if (name.equals(attribute.getValue())) {
+          return child;
+        }
+      }
+      return null;
+    }
+
+    public static <K, V> Map<K, V> getChildMap(Element element, String name) {
+      Element mapParent = getChildWithName(element, name);
+      if (mapParent != null) {
+        Element map = mapParent.getChild(MAP);
+        if (map != null) {
+          HashMap result = new HashMap();
+          for (Element entry : map.getChildren()) {
+            Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY);
+            Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE);
+            result.put(key, value);
+          }
+          return result;
+        }
+      }
+      return Collections.emptyMap();
+    }
+  }
+
+  public static class Json {
+
+    public static final String TASK_LIST = "task_list";
+    public static final String TASK_FILES = "task_files";
+
+    private Json() {
+    }
+
+    public static class CourseTypeAdapter implements JsonDeserializer<Course> {
+
+      private final File myCourseFile;
+
+      public CourseTypeAdapter(File courseFile) {
+        myCourseFile = courseFile;
+      }
+
+      @Override
+      public Course deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+        JsonObject courseObject = json.getAsJsonObject();
+        JsonArray lessons = courseObject.getAsJsonArray(LESSONS);
+        for (int lessonIndex = 1; lessonIndex <= lessons.size(); lessonIndex++) {
+          JsonObject lessonObject = lessons.get(lessonIndex - 1).getAsJsonObject();
+          JsonArray tasks = lessonObject.getAsJsonArray(TASK_LIST);
+          for (int taskIndex = 1; taskIndex <= tasks.size(); taskIndex++) {
+            JsonObject taskObject = tasks.get(taskIndex - 1).getAsJsonObject();
+            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));
+              if (resourceFile == null) {
+                continue;
+              }
+              Document document = FileDocumentManager.getInstance().getDocument(resourceFile);
+              if (document == null) {
+                continue;
+              }
+              JsonObject taskFileObject = taskFile.getValue().getAsJsonObject();
+              JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
+              for (JsonElement placeholder : placeholders) {
+                JsonObject placeholderObject = placeholder.getAsJsonObject();
+                if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) {
+                  break;
+                }
+                int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
+                int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
+                int offset = document.getLineStartOffset(line) + start;
+                placeholderObject.addProperty(OFFSET, offset);
+              }
+            }
+          }
+        }
+        return new GsonBuilder().create().fromJson(json, Course.class);
+      }
+    }
+
+    public static class StepicTaskFileAdapter implements JsonDeserializer<TaskFile> {
+
+      @Override
+      public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+        JsonObject taskFileObject = json.getAsJsonObject();
+        JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS);
+        for (JsonElement placeholder : placeholders) {
+          JsonObject placeholderObject = placeholder.getAsJsonObject();
+          int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt();
+          int start = placeholderObject.getAsJsonPrimitive(START).getAsInt();
+          if (line == -1) {
+            placeholderObject.addProperty(OFFSET, start);
+          } else {
+            Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString());
+            placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start);
+          }
+        }
+        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().fromJson(json, TaskFile.class);
+      }
+    }
+  }
+}
index 2690d4b86660a9ff196f2de8b7a4048258136316..ff870e726fac9a6889f39a54bce01804919eef05 100644 (file)
@@ -1,24 +1,26 @@
 package com.jetbrains.edu.learning;
 
+import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.components.State;
 import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.ui.JBColor;
 import com.intellij.util.containers.hash.HashMap;
 import com.intellij.util.xmlb.XmlSerializer;
 import com.intellij.util.xmlb.annotations.Transient;
-import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.courseFormat.*;
-import com.jetbrains.edu.learning.oldCourseFormat.OldCourse;
 import com.jetbrains.edu.learning.stepic.StepicUser;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jdom.Element;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -31,22 +33,30 @@ import java.util.Map;
 
 @State(name = "StudySettings", storages = @Storage("study_project.xml"))
 public class StudyTaskManager implements PersistentStateComponent<Element>, DumbAware {
+  private static final Logger LOG = Logger.getInstance(StudyTaskManager.class);
+  public static final int CURRENT_VERSION = 3;
+  private StepicUser myUser;
   private Course myCourse;
-  private OldCourse myOldCourse;
-  public int VERSION = 2;
-  public StepicUser myUser;
-  public Map<AnswerPlaceholder, StudyStatus> myStudyStatusMap = new HashMap<>();
-  public Map<TaskFile, StudyStatus> myTaskStatusMap = new HashMap<>();
+  public int VERSION = 3;
+
   public Map<Task, List<UserTest>> myUserTests = new HashMap<>();
   public List<String> myInvisibleFiles = new ArrayList<>();
+
   public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx();
   private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT;
   private boolean myTurnEditingMode = false;
 
-  private StudyTaskManager() {
+  @Transient private final Project myProject;
+
+  public StudyTaskManager(Project project) {
+    myProject = project;
   }
 
-  public void setCourse(final Course course) {
+  public StudyTaskManager() {
+    this(null);
+  }
+
+  public void setCourse(Course course) {
     myCourse = course;
   }
 
@@ -85,97 +95,8 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
     }
   }
 
-
-  public void setStatus(Task task, StudyStatus status) {
-    task.setStatus(status);
-    for (TaskFile taskFile : task.getTaskFiles().values()) {
-      setStatus(taskFile, status);
-    }
-  }
-
-  public void setStatus(TaskFile file, StudyStatus status) {
-    for (AnswerPlaceholder answerPlaceholder : file.getAnswerPlaceholders()) {
-      setStatus(answerPlaceholder, status);
-    }
-  }
-
-  public StudyStatus getStatus(AnswerPlaceholder placeholder) {
-    StudyStatus status = placeholder.getStatus();
-    if (status != StudyStatus.Uninitialized) return status;
-    
-    status = myStudyStatusMap.get(placeholder);
-    if (status == null) {
-      status = StudyStatus.Unchecked;
-    }
-    placeholder.setStatus(status);
-    return status;
-  }
-
-
-  public StudyStatus getStatus(@NotNull final Lesson lesson) {
-    for (Task task : lesson.getTaskList()) {
-      StudyStatus taskStatus = getStatus(task);
-      if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) {
-        return StudyStatus.Unchecked;
-      }
-    }
-    return StudyStatus.Solved;
-  }
-
-  public StudyStatus getStatus(@NotNull final Task task) {
-    StudyStatus taskStatus = task.getStatus();
-    if (taskStatus != StudyStatus.Uninitialized) return taskStatus;
-    
-    for (TaskFile taskFile : task.getTaskFiles().values()) {
-      StudyStatus taskFileStatus = getStatus(taskFile);
-      if (taskFileStatus == StudyStatus.Unchecked) {
-        task.setStatus(StudyStatus.Unchecked);
-        removeObsoleteTaskStatus(task);
-        return StudyStatus.Unchecked;
-      }
-      if (taskFileStatus == StudyStatus.Failed) {
-        task.setStatus(StudyStatus.Failed);
-        removeObsoleteTaskStatus(task);
-        return StudyStatus.Failed;
-      }
-    }
-    task.setStatus(StudyStatus.Solved);
-    removeObsoleteTaskStatus(task);
-    return StudyStatus.Solved;
-  }
-  
-  private void removeObsoleteTaskStatus(Task task) {
-    for (TaskFile taskFile: task.taskFiles.values()) {
-      myTaskStatusMap.remove(taskFile);
-      
-      for (AnswerPlaceholder answerPlaceholder: taskFile.getAnswerPlaceholders()) {
-        myStudyStatusMap.remove(answerPlaceholder);
-      }
-    }
-    
-  }
-
-  private StudyStatus getStatus(@NotNull final TaskFile file) {
-    if (file.getAnswerPlaceholders().isEmpty()) {
-      if (myTaskStatusMap == null) return StudyStatus.Solved;
-      return myTaskStatusMap.get(file);
-
-    }
-    for (AnswerPlaceholder answerPlaceholder : file.getAnswerPlaceholders()) {
-      StudyStatus placeholderStatus = getStatus(answerPlaceholder);
-      if (placeholderStatus == StudyStatus.Failed) {
-        return StudyStatus.Failed;
-      }
-      if (placeholderStatus == StudyStatus.Unchecked) {
-        return StudyStatus.Unchecked;
-      }
-    }
-    return StudyStatus.Solved;
-  }
-
-
   public JBColor getColor(@NotNull final AnswerPlaceholder placeholder) {
-    final StudyStatus status = getStatus(placeholder);
+    final StudyStatus status = placeholder.getStatus();
     if (status == StudyStatus.Solved) {
       return JBColor.GREEN;
     }
@@ -186,7 +107,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
   }
 
   public boolean hasFailedAnswerPlaceholders(@NotNull final TaskFile taskFile) {
-    return taskFile.getAnswerPlaceholders().size() > 0 && getStatus(taskFile) == StudyStatus.Failed;
+    return taskFile.getAnswerPlaceholders().size() > 0 && taskFile.hasFailedPlaceholders();
   }
 
   @Nullable
@@ -194,7 +115,7 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
   public Element getState() {
     Element el = new Element("taskManager");
     if (myCourse != null) {
-      Element courseElement = new Element(MAIN_ELEMENT);
+      Element courseElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT);
       XmlSerializer.serializeInto(this, courseElement);
       el.addContent(courseElement);
     }
@@ -203,46 +124,34 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
 
   @Override
   public void loadState(Element state) {
-    final Element mainElement = state.getChild(MAIN_ELEMENT);
-    if (mainElement != null) {
-      final StudyTaskManager taskManager = XmlSerializer.deserialize(mainElement, StudyTaskManager.class);
-      if (taskManager != null) {
-        myCourse = taskManager.myCourse;
-        myUserTests = taskManager.myUserTests;
-        myInvisibleFiles = taskManager.myInvisibleFiles;
-        myTaskStatusMap = taskManager.myTaskStatusMap;
-        myStudyStatusMap = taskManager.myStudyStatusMap;
-        myShouldUseJavaFx = taskManager.myShouldUseJavaFx;
-        myUser = taskManager.getUser();
-      }
-    }
-    final Element oldCourseElement = state.getChild(COURSE_ELEMENT);
-    if (oldCourseElement != null) {
-      myOldCourse = XmlSerializer.deserialize(oldCourseElement, OldCourse.class);
-      if (myOldCourse != null) {
-        myCourse = EduUtils.transformOldCourse(myOldCourse, pair -> {
-          setStatus(pair.first, pair.second);
-          return null;
-        });
-        myOldCourse = null;
-      }
-    }
+    int version = StudySerializationUtils.Xml.getVersion(state);
+    if (version == -1) {
+      LOG.error("StudyTaskManager doesn't contain any version:\n" + state.getValue());
+      return;
+    }
+    switch (version) {
+      case 1:
+        state = StudySerializationUtils.Xml.convertToSecondVersion(state);
+      case 2:
+        state = StudySerializationUtils.Xml.convertToThirdVersion(state, myProject);
+      //uncomment for future versions
+      //case 3:
+      //state = StudySerializationUtils.Xml.convertToForthVersion(state, myProject);
+    }
+
+    XmlSerializer.deserializeInto(this, state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT));
+    VERSION = CURRENT_VERSION;
     if (myCourse != null) {
       myCourse.initCourse(true);
+      if (version != VERSION) {
+        String updatedCoursePath = FileUtil.join(PathManager.getConfigPath(), "courses", myCourse.getName());
+        if (new File(updatedCoursePath).exists()) {
+          myCourse.setCourseDirectory(updatedCoursePath);
+        }
+      }
     }
   }
 
-  public static final String COURSE_ELEMENT = "courseElement";
-  public static final String MAIN_ELEMENT = "StudyTaskManager";
-
-  public OldCourse getOldCourse() {
-    return myOldCourse;
-  }
-
-  public void setOldCourse(OldCourse oldCourse) {
-    myOldCourse = oldCourse;
-  }
-
   public static StudyTaskManager getInstance(@NotNull final Project project) {
     return ServiceManager.getService(project, StudyTaskManager.class);
   }
@@ -279,13 +188,15 @@ public class StudyTaskManager implements PersistentStateComponent<Element>, Dumb
     myTurnEditingMode = turnEditingMode;
   }
 
+  @Transient
   public String getLogin() {
     if (myUser != null) {
       return myUser.getEmail();
     }
     return "";
   }
-  
+
+  @Transient
   public void setLogin(String login) {
     if (myUser != null) {
       myUser.setEmail(login);
index 8cedfc1dadfa302bcc4cdb79764104103933fd4e..d8d4276575bc83a84f00b347a39163a2df29cb8c 100644 (file)
@@ -28,6 +28,7 @@ import com.intellij.openapi.ui.popup.Balloon;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.wm.ToolWindow;
@@ -680,4 +681,17 @@ public class StudyUtils {
       return new File(parent, EduNames.TASK_MD);
     }
   }
+
+  @Nullable
+  public static Document getDocument(String basePath, int lessonIndex, int taskIndex, String fileName) {
+    String taskPath = FileUtil.join(basePath, EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex);
+    VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, fileName));
+    if (taskFile == null) {
+      taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, fileName));
+    }
+    if (taskFile == null) {
+      return null;
+    }
+    return FileDocumentManager.getInstance().getDocument(taskFile);
+  }
 }
index dced38434bda5031f3e89c085d4e58ebfe8419a7..0427f690c82558da9455f3ce8f82d7371ed75682 100644 (file)
@@ -34,7 +34,7 @@ public class StudyFillPlaceholdersAction extends AnAction {
           if (answer == null) {
             continue;
           }
-          int offset = placeholder.getRealStartOffset(document);
+          int offset = placeholder.getOffset();
           document.deleteString(offset, offset + placeholder.getRealLength());
           document.insertString(offset, answer);
         }
index b05088812f2c250323164f92ef7eac7b2e614821..987819c83ffe6b320216e1e465b75acdac16e504 100644 (file)
@@ -50,11 +50,11 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
       return;
     }
     AnswerPlaceholder.MyInitialState initialState = answerPlaceholder.getInitialState();
-    int startOffset = patternDocument.getLineStartOffset(initialState.myLine) + initialState.myStart;
-    final String text = patternDocument.getText(new TextRange(startOffset, startOffset + initialState.myLength));
+    int startOffset = initialState.getOffset();
+    final String text = patternDocument.getText(new TextRange(startOffset, startOffset + initialState.getLength()));
     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
       Document document = studyState.getEditor().getDocument();
-      int offset = answerPlaceholder.getRealStartOffset(document);
+      int offset = answerPlaceholder.getOffset();
       document.deleteString(offset, offset + answerPlaceholder.getRealLength());
       document.insertString(offset, text);
     }), NAME, null);
@@ -96,6 +96,6 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction {
     }
     final Editor editor = studyState.getEditor();
     final TaskFile taskFile = studyState.getTaskFile();
-    return taskFile.getAnswerPlaceholder(editor.getDocument(), editor.getCaretModel().getLogicalPosition());
+    return taskFile.getAnswerPlaceholder(editor.getCaretModel().getOffset());
   }
 }
index 75a1277c88d990941fe13741f57c13a1f2874fef..ab390c5b04052886e01af24139d4599958aff9f2 100644 (file)
@@ -5,7 +5,6 @@ import com.intellij.codeInsight.documentation.DocumentationManager;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.KeyboardShortcut;
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.keymap.KeymapUtil;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.popup.JBPopup;
@@ -14,11 +13,11 @@ import com.intellij.openapi.util.Disposer;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
-import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
-import com.jetbrains.edu.learning.courseFormat.Course;
 import com.jetbrains.edu.learning.StudyState;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
+import com.jetbrains.edu.learning.courseFormat.Course;
 import icons.InteractiveLearningIcons;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -54,8 +53,9 @@ public class StudyShowHintAction extends StudyActionWithShortcut {
     }
     PsiFile file = PsiManager.getInstance(project).findFile(studyState.getVirtualFile());
     final Editor editor = studyState.getEditor();
-    LogicalPosition pos = editor.getCaretModel().getLogicalPosition();
-    AnswerPlaceholder answerPlaceholder = studyState.getTaskFile().getAnswerPlaceholder(editor.getDocument(), pos);
+    int offset = editor.getCaretModel().getOffset();
+    AnswerPlaceholder answerPlaceholder = studyState.getTaskFile().getAnswerPlaceholder(
+      offset);
     if (file == null) {
       return;
     }
@@ -64,7 +64,6 @@ public class StudyShowHintAction extends StudyActionWithShortcut {
       String hint = answerPlaceholder.getHint();
       hintText = hint.isEmpty() ? HINT_NOT_AVAILABLE : hint;
     }
-    int offset = editor.getDocument().getLineStartOffset(pos.line) + pos.column;
     PsiElement element = file.findElementAt(offset);
     DocumentationManager documentationManager = DocumentationManager.getInstance(project);
     DocumentationComponent component = new DocumentationComponent(documentationManager);
index cd0b5ccf1dbe63c6bfd99d136625f6f78798921a..2bffbe8cafca152ac9fa4b85964f30edc4e01cd4 100644 (file)
@@ -2,14 +2,13 @@ package com.jetbrains.edu.learning.actions;
 
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.navigation.StudyNavigator;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -38,7 +37,7 @@ abstract public class StudyWindowNavigationAction extends StudyActionWithShortcu
             if (nextAnswerPlaceholder == null) {
               return;
             }
-            StudyNavigator.navigateToAnswerPlaceholder(selectedEditor, nextAnswerPlaceholder, selectedTaskFile);
+            StudyNavigator.navigateToAnswerPlaceholder(selectedEditor, nextAnswerPlaceholder);
             selectedEditor.getSelectionModel().removeSelection();
             }
           }
@@ -47,8 +46,7 @@ abstract public class StudyWindowNavigationAction extends StudyActionWithShortcu
 
   @Nullable
   private static AnswerPlaceholder getSelectedAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile file) {
-    LogicalPosition position = editor.getCaretModel().getLogicalPosition();
-    return file.getAnswerPlaceholder(editor.getDocument(), position);
+    return file.getAnswerPlaceholder(editor.getCaretModel().getOffset());
   }
 
   @Nullable
index 965347321167aa794bd6913756c4dc759f9d957f..90b6110df848bbdc3aa665b1f2690a202660883d 100644 (file)
@@ -49,7 +49,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
     myTask = studyState.getTask();
     myTaskDir = studyState.getTaskDir();
     myTaskManger = StudyTaskManager.getInstance(myProject);
-    myStatusBeforeCheck = myTaskManger.getStatus(myTask);
+    myStatusBeforeCheck = myTask.getStatus();
   }
 
   @Override
@@ -67,7 +67,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
 
   @Override
   public void onCancel() {
-    myTaskManger.setStatus(myTask, myStatusBeforeCheck);
+    myTask.setStatus(myStatusBeforeCheck);
     clearState();
   }
 
@@ -156,9 +156,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
   }
 
   protected void onTaskFailed(String message) {
-    myTaskManger.setStatus(myTask, StudyStatus.Failed);
     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
-
+    myTask.setStatus(StudyStatus.Failed);
     if (course != null) {
       if (course.isAdaptive()) {
         ApplicationManager.getApplication().invokeLater(
@@ -175,9 +174,8 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun
   }
 
   protected void onTaskSolved(String message) {
-    myTaskManger.setStatus(myTask, StudyStatus.Solved);
     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
-
+    myTask.setStatus(StudyStatus.Solved);
     if (course != null) {
       if (course.isAdaptive()) {
         ApplicationManager.getApplication().invokeLater(
index b91bc4773ba4e15190a3fa6faeac5ff592cd871c..7e62ccd1c0382076e1d14f523eca3b72d92cb6fe 100644 (file)
@@ -156,7 +156,7 @@ public class StudyCheckUtils {
           if (!answerPlaceholder.isValid(document)) {
             continue;
           }
-          final int start = answerPlaceholder.getRealStartOffset(document);
+          final int start = answerPlaceholder.getOffset();
           final int end = start + answerPlaceholder.getRealLength();
           final String text = answerPlaceholder.getPossibleAnswer();
           document.replaceString(start, end, text);
index c75904d0e3a54cb0a162f666aedcc1be3ca9f540..9621efbd7d397a2d10b5d468c4a3c04c509addd3 100644 (file)
@@ -54,10 +54,10 @@ public class StudySmartChecker {
         TaskFile.copy(answerTaskFile, windowTaskFile);
         EduDocumentListener listener = new EduDocumentListener(windowTaskFile);
         windowDocument.addDocumentListener(listener);
-        int start = placeholder.getRealStartOffset(windowDocument);
+        int start = placeholder.getOffset();
         int end = start + placeholder.getRealLength();
         final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex());
-        int userStart = userAnswerPlaceholder.getRealStartOffset(usersDocument);
+        int userStart = userAnswerPlaceholder.getOffset();
         int userEnd = userStart + userAnswerPlaceholder.getRealLength();
         String text = usersDocument.getText(new TextRange(userStart, userEnd));
         windowDocument.replaceString(start, end, text);
index 81fcf85fb989d219a8c8a02e360a74f1f127439d..3902b942af009246346dd399b2cd6de49dedf35b 100644 (file)
@@ -36,7 +36,10 @@ public class EduAnswerPlaceholderPainter {
                                                                     EffectType.BOXED, Font.PLAIN);
     final Project project = editor.getProject();
     assert project != null;
-    final int startOffset = placeholder.getRealStartOffset(document);
+    final int startOffset = placeholder.getOffset();
+    if (startOffset == - 1) {
+      return;
+    }
     final int length = placeholder.getRealLength();
     final int endOffset = startOffset + length;
     textAttributes.setEffectColor(color);
@@ -66,7 +69,7 @@ public class EduAnswerPlaceholderPainter {
       DocumentImpl documentImpl = (DocumentImpl)document;
       List<RangeMarker> blocks = documentImpl.getGuardedBlocks();
       if (!placeholder.isValid(document)) return;
-      int start = placeholder.getRealStartOffset(document);
+      int start = placeholder.getOffset();
       final int length = placeholder.getRealLength();
       int end = start + length;
       if (start != 0) {
index 8e43bf57b26b55bca2c39011e2da909bfdc8a918..7105b4662b300d6e671ef0d63b7e2cb34395458f 100644 (file)
@@ -39,10 +39,9 @@ public class EduDocumentListener extends DocumentAdapter {
       return;
     }
     myTaskFile.setHighlightErrors(true);
-    Document document = e.getDocument();
     myAnswerPlaceholders.clear();
     for (AnswerPlaceholder answerPlaceholder : myTaskFile.getAnswerPlaceholders()) {
-      int twStart = answerPlaceholder.getRealStartOffset(document);
+      int twStart = answerPlaceholder.getOffset();
       int length = answerPlaceholder.getRealLength();
       int twEnd = twStart + length;
       myAnswerPlaceholders.add(new AnswerPlaceholderWrapper(answerPlaceholder, twStart, twEnd));
@@ -70,11 +69,9 @@ public class EduDocumentListener extends DocumentAdapter {
           twEnd += change;
         }
         AnswerPlaceholder answerPlaceholder = answerPlaceholderWrapper.getAnswerPlaceholder();
-        int line = document.getLineNumber(twStart);
-        int start = twStart - document.getLineStartOffset(line);
         int length = twEnd - twStart;
-        answerPlaceholder.setLine(line);
-        answerPlaceholder.setStart(start);
+        answerPlaceholder.setOffset(twStart);
+        answerPlaceholder.setLength(length);
         if (!answerPlaceholder.getUseLength()) {
           answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length)));
         }
index fb5df916db6c7044574e747387591161970ed87c..ecfa1ec3a13619158b0e49774fc8a986d32c17ad 100644 (file)
@@ -4,33 +4,24 @@ import com.intellij.ide.SaveAndSyncHandler;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
 import com.intellij.psi.PsiDirectory;
-import com.intellij.util.Function;
-import com.intellij.util.containers.HashMap;
 import com.jetbrains.edu.learning.courseFormat.*;
-import com.jetbrains.edu.learning.oldCourseFormat.OldCourse;
-import com.jetbrains.edu.learning.oldCourseFormat.TaskWindow;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.imageio.ImageIO;
-import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Map;
@@ -95,7 +86,7 @@ public class EduUtils {
             printWriter.println("#educational_plugin_window = ");
             continue;
           }
-          int start = answerPlaceholder.getRealStartOffset(document);
+          int start = answerPlaceholder.getOffset();
           final String windowDescription = document.getText(new TextRange(start, start + length));
           printWriter.println("#educational_plugin_window = " + windowDescription);
         }
@@ -186,8 +177,9 @@ public class EduUtils {
     taskFile.sortAnswerPlaceholders();
     for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) {
       final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i);
-      if (answerPlaceholder.getRealStartOffset(document) > document.getTextLength() || answerPlaceholder.getRealStartOffset(document) + answerPlaceholder.getPossibleAnswerLength() > document.getTextLength()) {
-        LOG.error("Wrong startOffset: " + answerPlaceholder.getRealStartOffset(document) + "; document: " + file.getPath());
+      int offset = answerPlaceholder.getOffset();
+      if (offset > document.getTextLength() || offset + answerPlaceholder.getPossibleAnswerLength() > document.getTextLength()) {
+        LOG.error("Wrong startOffset: " + answerPlaceholder.getOffset() + "; document: " + file.getPath());
         return;
       }
       replaceAnswerPlaceholder(project, document, answerPlaceholder);
@@ -200,7 +192,7 @@ public class EduUtils {
                                                @NotNull final Document document,
                                                @NotNull final AnswerPlaceholder answerPlaceholder) {
     final String taskText = answerPlaceholder.getTaskText();
-    final int offset = answerPlaceholder.getRealStartOffset(document);
+    final int offset = answerPlaceholder.getOffset();
     CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
       document.replaceString(offset, offset + answerPlaceholder.getPossibleAnswerLength(), taskText);
       FileDocumentManager.getInstance().saveDocument(document);
@@ -246,76 +238,6 @@ public class EduUtils {
     return lesson.getTask(directory.getName());
   }
 
-  @NotNull
-  public static Course transformOldCourse(@NotNull final OldCourse oldCourse) {
-    return transformOldCourse(oldCourse, null);
-  }
-
-  @NotNull
-  public static Course transformOldCourse(@NotNull final OldCourse oldCourse,
-                                          @Nullable Function<Pair<AnswerPlaceholder, StudyStatus>, Void> setStatus) {
-    Course course = new Course();
-    course.setDescription(oldCourse.description);
-    course.setName(oldCourse.name);
-    course.setAuthors(new String[]{oldCourse.author});
-
-    String updatedCoursePath = FileUtil.join(PathManager.getConfigPath(), "courses", oldCourse.name);
-    if (new File(updatedCoursePath).exists()) {
-      course.setCourseDirectory(FileUtil.toSystemIndependentName(updatedCoursePath));
-    }
-    final ArrayList<Lesson> lessons = new ArrayList<Lesson>();
-    for (com.jetbrains.edu.learning.oldCourseFormat.Lesson oldLesson : oldCourse.lessons) {
-      final Lesson lesson = new Lesson();
-      lesson.setName(oldLesson.name);
-      lesson.setIndex(oldLesson.myIndex + 1);
-
-      final ArrayList<Task> tasks = new ArrayList<Task>();
-      for (com.jetbrains.edu.learning.oldCourseFormat.Task oldTask : oldLesson.taskList) {
-        final Task task = new Task();
-        task.setIndex(oldTask.myIndex + 1);
-        task.setName(oldTask.name);
-        task.setLesson(lesson);
-        final HashMap<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
-        for (Map.Entry<String, com.jetbrains.edu.learning.oldCourseFormat.TaskFile> entry : oldTask.taskFiles.entrySet()) {
-          final TaskFile taskFile = new TaskFile();
-          final com.jetbrains.edu.learning.oldCourseFormat.TaskFile oldTaskFile = entry.getValue();
-          taskFile.setIndex(oldTaskFile.myIndex);
-          taskFile.name = entry.getKey();
-
-          final ArrayList<AnswerPlaceholder> placeholders = new ArrayList<AnswerPlaceholder>();
-          for (TaskWindow window : oldTaskFile.taskWindows) {
-            final AnswerPlaceholder placeholder = new AnswerPlaceholder();
-            placeholder.setIndex(window.myIndex);
-            placeholder.setHint(window.hint);
-            placeholder.setLength(window.length);
-            placeholder.setLine(window.line);
-            placeholder.setPossibleAnswer(window.possibleAnswer);
-            placeholder.setStart(window.start);
-            placeholders.add(placeholder);
-            placeholder.setInitialState(new AnswerPlaceholder.MyInitialState(window.myInitialLine,
-                                                                             window.myInitialLength,
-                                                                             window.myInitialStart));
-            if (setStatus != null) {
-              setStatus.fun(Pair.create(placeholder, window.myStatus));
-            }
-          }
-
-          taskFile.setAnswerPlaceholders(placeholders);
-          taskFiles.put(entry.getKey(), taskFile);
-        }
-        task.taskFiles = taskFiles;
-        tasks.add(task);
-      }
-
-      lesson.taskList = tasks;
-
-      lessons.add(lesson);
-    }
-    course.setLessons(lessons);
-    course.initCourse(true);
-    return course;
-  }
-
   public static boolean isImage(String fileName) {
     final String[] readerFormatNames = ImageIO.getReaderFormatNames();
     for (@NonNls String format : readerFormatNames) {
index 72f2b422b7f8be655d2dfd899686e0cc5b02108a..23fb46888d439e97087b31fcfa38bcd086974392 100644 (file)
@@ -13,25 +13,34 @@ import org.jetbrains.annotations.Nullable;
 
 public class AnswerPlaceholder {
 
-  @Expose private int line = 0;
-  @Expose private int start = 0;
   @Expose private String hint = "";
 
   @SerializedName("possible_answer")
   @Expose private String possibleAnswer = "";
-  @Expose private int length = 0;
+
+  @SerializedName("offset")
+  @Expose
+  private int myOffset = -1;
+
+  @Expose private int length = -1;
+
   private int myIndex = -1;
   private String myTaskText;
   private MyInitialState myInitialState;
-  private StudyStatus myStatus = StudyStatus.Uninitialized;
+  private StudyStatus myStatus = StudyStatus.Unchecked;
   private boolean mySelected = false;
   private boolean myUseLength = true;
 
-  @Transient private TaskFile myTaskFile;
+
+  @Transient
+  private TaskFile myTaskFile;
+
+  public AnswerPlaceholder() {
+  }
 
   public void initAnswerPlaceholder(final TaskFile file, boolean isRestarted) {
     if (!isRestarted) {
-      setInitialState(new MyInitialState(getLine(), getLength(), getStart()));
+      setInitialState(new MyInitialState(myOffset, length));
       myStatus = file.getTask().getStatus();
     }
 
@@ -57,22 +66,6 @@ public class AnswerPlaceholder {
     this.length = length;
   }
 
-  public int getStart() {
-    return start;
-  }
-
-  public void setStart(int start) {
-    this.start = start;
-  }
-
-  public int getLine() {
-    return line;
-  }
-
-  public void setLine(int line) {
-    this.line = line;
-  }
-
   public String getHint() {
     return hint;
   }
@@ -93,7 +86,7 @@ public class AnswerPlaceholder {
     return myInitialState;
   }
 
-  public void setInitialState(@NotNull final MyInitialState initialState) {
+  public void setInitialState(MyInitialState initialState) {
     myInitialState = initialState;
   }
 
@@ -115,10 +108,6 @@ public class AnswerPlaceholder {
     myTaskFile = taskFile;
   }
 
-  public int getRealStartOffset(@NotNull final Document document) {
-    return document.getLineStartOffset(line) + start;
-  }
-
   public int getPossibleAnswerLength() {
     return possibleAnswer.length();
   }
@@ -128,21 +117,16 @@ public class AnswerPlaceholder {
   }
 
   public boolean isValid(@NotNull final Document document, int length) {
-    boolean isLineValid = line < document.getLineCount() && line >= 0;
-    if (!isLineValid) return false;
-    boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line);
-    boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength();
-    return isLengthValid && isStartValid;
+    return myOffset >= 0 && myOffset + length <= document.getTextLength();
   }
 
   /**
    * Returns window to its initial state
    */
   public void reset() {
-    line = myInitialState.myLine;
-    start = myInitialState.myStart;
-    length = myInitialState.myLength;
-    if (!getUseLength()) {
+    myOffset = myInitialState.getOffset();
+    length = myInitialState.getLength();
+    if (!myUseLength) {
       possibleAnswer = myTaskText;
     }
   }
@@ -164,7 +148,7 @@ public class AnswerPlaceholder {
   }
 
   public void init() {
-    setInitialState(new MyInitialState(line, myTaskText.length(), start));
+    setInitialState(new MyInitialState(myOffset, myTaskText.length()));
   }
 
   public boolean getUseLength() {
@@ -182,53 +166,40 @@ public class AnswerPlaceholder {
     myUseLength = useLength;
   }
 
-  public void setInitialState(AnswerPlaceholder placeholder) {
-    setInitialState(new MyInitialState(placeholder.line, placeholder.length, placeholder.start));
+  public int getOffset() {
+    return myOffset;
+  }
+
+  public void setOffset(int offset) {
+    myOffset = offset;
   }
 
   public static class MyInitialState {
-    public int myLine = -1;
-    public int myLength = -1;
-    public int myStart = -1;
+    private int length = -1;
+    private int offset = -1;
 
     public MyInitialState() {
     }
 
-    public MyInitialState(int line, int length, int start) {
-      myLine = line;
-      myLength = length;
-      myStart = start;
+    public MyInitialState(int initialOffset, int length) {
+      this.offset = initialOffset;
+      this.length = length;
     }
-  }
 
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    AnswerPlaceholder that = (AnswerPlaceholder)o;
+    public int getLength() {
+      return length;
+    }
 
-    if (getLine() != that.getLine()) return false;
-    if (getStart() != that.getStart()) return false;
-    if (getLength() != that.getLength()) return false;
-    if (getIndex() != that.getIndex()) return false;
-    if (getHint() != null ? !getHint().equals(that.getHint()) : that.getHint() != null) return false;
-    if (getPossibleAnswer() != null ? !getPossibleAnswer().equals(that.getPossibleAnswer()) : that.getPossibleAnswer() != null)
-      return false;
-    if (myTaskText != null ? !myTaskText.equals(that.myTaskText) : that.myTaskText != null) return false;
+    public void setLength(int length) {
+      this.length = length;
+    }
 
-    return true;
-  }
+    public int getOffset() {
+      return offset;
+    }
 
-  @Override
-  public int hashCode() {
-    int result = getLine();
-    result = 31 * result + getStart();
-    result = 31 * result + (getHint() != null ? getHint().hashCode() : 0);
-    result = 31 * result + (getPossibleAnswer() != null ? getPossibleAnswer().hashCode() : 0);
-    result = 31 * result + getLength();
-    result = 31 * result + getIndex();
-    result = 31 * result + (myTaskText != null ? myTaskText.hashCode() : 0);
-    return result;
+    public void setOffset(int offset) {
+      this.offset = offset;
+    }
   }
 }
index 09926490d8e23f67d7656f4404bdf21546231745..90cc4d8f8ba7a0799bb8d5dbb7fe217db357ef71 100644 (file)
@@ -5,12 +5,7 @@ import java.util.Comparator;
 public class AnswerPlaceholderComparator implements Comparator<AnswerPlaceholder> {
   @Override
   public int compare(AnswerPlaceholder o1, AnswerPlaceholder answerPlaceholder) {
-    final int line = o1.getLine();
-    int lineDiff = line - answerPlaceholder.getLine();
-    if (lineDiff == 0) {
-      return o1.getStart() - answerPlaceholder.getStart();
-    }
-    return lineDiff;
+    return o1.getOffset() - answerPlaceholder.getOffset();
   }
 
   @Override
index 03dbc110882c5f6d523a4f212f7b324887bc2f9b..bfd62a40236ba516e2a6c5143a4e119be6e8f781 100644 (file)
@@ -4,7 +4,6 @@ import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
 import com.intellij.lang.Language;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.Function;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
 import com.jetbrains.edu.learning.stepic.StepicUser;
@@ -29,6 +28,9 @@ public class Course {
   @Expose private String courseType = EduNames.PYCHARM;
   @Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes
 
+  public Course() {
+  }
+
   /**
    * Initializes state of course
    */
index 4110dd621c28d800c6d0fb8079deeaea0aceba08..48103d22f78bb1b7c3620bbf304be7ea3142cef1 100644 (file)
@@ -12,12 +12,27 @@ import java.util.List;
 
 public class Lesson implements StudyItem {
   @Expose public int id;
-  @Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number
-  @Expose @Transient public List<Integer> steps;
-  @Expose @SerializedName("title") private String name;
-  @Expose @SerializedName("task_list") public List<Task> taskList = new ArrayList<Task>();
+  @Transient public List<Integer> steps;
   @Transient public List<String> tags;
-  @Transient private Course myCourse = null;
+  @Transient
+  Boolean is_public;
+
+  @Expose
+  @SerializedName("title")
+  private String name;
+
+  @Expose
+  @SerializedName("task_list")
+  public List<Task> taskList = new ArrayList<Task>();
+
+  @Transient
+  private Course myCourse = null;
+
+  // index is visible to user number of lesson from 1 to lesson number
+  @Expose private int myIndex = -1;
+
+  public Lesson() {
+  }
 
   public void initLesson(final Course course, boolean isRestarted) {
     setCourse(course);
@@ -74,4 +89,12 @@ public class Lesson implements StudyItem {
     return null;
   }
 
+  public StudyStatus getStatus() {
+    for (Task task : taskList) {
+      if (task.getStatus() != StudyStatus.Solved) {
+        return StudyStatus.Unchecked;
+      }
+    }
+    return StudyStatus.Solved;
+  }
 }
index e1849da8461bd3d12a965d2b83d83f58a045547e..b4e1b97587bc4986164714c3d0ea1c3e87ef3851 100644 (file)
@@ -1,5 +1,5 @@
 package com.jetbrains.edu.learning.courseFormat;
 
 public enum StudyStatus {
-  Unchecked, Solved, Failed, Uninitialized
+  Unchecked, Solved, Failed
 }
index 8cb75e13ee3c55ed60a66e9a832a65f589995aa5..bbd515843705f46b37293c9644139f226313dd7d 100644 (file)
@@ -20,13 +20,22 @@ import java.util.Map;
  * Implementation of task which contains task files, tests, input file for tests
  */
 public class Task implements StudyItem {
-  @Expose private String name;
-  @Expose private String text;
+  @Expose
+  private String name;
+
+  // index is visible to user number of task from 1 to task number
+  @Expose private int myIndex;
+  @Expose private StudyStatus myStatus = StudyStatus.Unchecked;
+
   @Expose private int myStepicId;
-  @Expose private int myIndex; // index is visible to user number of task from 1 to task number
-  @Expose @SerializedName("task_files") public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
-  @Expose private Map<String, String> testsText = new HashMap<String, String>();
-  @Expose private StudyStatus myStatus = StudyStatus.Uninitialized;
+
+  @Expose
+  @SerializedName("task_files")
+  public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
+
+  @Expose private String text;
+  private Map<String, String> testsText = new HashMap<String, String>();
+
   @Transient private Lesson myLesson;
 
   public Task() {}
@@ -205,5 +214,10 @@ public class Task implements StudyItem {
   
   public void setStatus(StudyStatus status) {
     myStatus = status;
+    for (TaskFile taskFile : taskFiles.values()) {
+      for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) {
+        placeholder.setStatus(status);
+      }
+    }
   }
 }
index 65ef81d5e6b53b8902692c8cf1d256d5dc2ecb6c..81a126735ac1a2546ab4a8594fc1bdebb28af3fa 100644 (file)
@@ -2,8 +2,6 @@ package com.jetbrains.edu.learning.courseFormat;
 
 import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.util.xmlb.annotations.Transient;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -27,6 +25,9 @@ public class TaskFile {
   @Expose @SerializedName("placeholders") private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
   @Transient private Task myTask;
 
+  public TaskFile() {
+  }
+
   public void initTaskFile(final Task task, boolean isRestarted) {
     setTask(task);
     final List<AnswerPlaceholder> answerPlaceholders = getAnswerPlaceholders();
@@ -70,32 +71,16 @@ public class TaskFile {
   }
 
   /**
-   * @param pos position in editor
-   * @return task window located in specified position or null if there is no task window in this position
+   * @param offset position in editor
+   * @return answer placeholder located in specified position or null if there is no task window in this position
    */
   @Nullable
-  public AnswerPlaceholder getAnswerPlaceholder(@NotNull final Document document, @NotNull final LogicalPosition pos) {
-    return getAnswerPlaceholder(document, pos, false);
-  }
-
-  @Nullable
-  public AnswerPlaceholder getAnswerPlaceholder(@NotNull final Document document, @NotNull final LogicalPosition pos,
-                                                boolean useAnswerLength) {
-    int line = pos.line;
-    if (line >= document.getLineCount()) {
-      return null;
-    }
-    int column = pos.column;
-    int offset = document.getLineStartOffset(line) + column;
+  public AnswerPlaceholder getAnswerPlaceholder(int offset) {
     for (AnswerPlaceholder placeholder : myAnswerPlaceholders) {
-      if (placeholder.getLine() <= line) {
-        int realStartOffset = placeholder.getRealStartOffset(document);
-        int placeholderLength = placeholder.getRealLength();
-        final int length = placeholderLength > 0 ? placeholderLength : 0;
-        int endOffset = realStartOffset + length;
-        if (realStartOffset <= offset && offset <= endOffset) {
-          return placeholder;
-        }
+      int placeholderStart = placeholder.getOffset();
+      int placeholderEnd = placeholderStart + placeholder.getRealLength();
+      if (placeholderStart <= offset && offset <= placeholderEnd) {
+        return placeholder;
       }
     }
     return null;
@@ -107,9 +92,8 @@ public class TaskFile {
     List<AnswerPlaceholder> answerPlaceholdersCopy = new ArrayList<AnswerPlaceholder>(sourceAnswerPlaceholders.size());
     for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) {
       AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder();
-      answerPlaceholderCopy.setLine(answerPlaceholder.getLine());
-      answerPlaceholderCopy.setStart(answerPlaceholder.getStart());
       answerPlaceholderCopy.setTaskText(answerPlaceholder.getTaskText());
+      answerPlaceholderCopy.setOffset(answerPlaceholder.getOffset());
       answerPlaceholderCopy.setLength(answerPlaceholder.getLength());
       answerPlaceholderCopy.setPossibleAnswer(answerPlaceholder.getPossibleAnswer());
       answerPlaceholderCopy.setIndex(answerPlaceholder.getIndex());
@@ -151,35 +135,12 @@ public class TaskFile {
     }
   }
 
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    TaskFile that = (TaskFile)o;
-
-    if (getIndex() != that.getIndex()) return false;
-    if (!name.equals(that.name)) return false;
-
-    final List<AnswerPlaceholder> answerPlaceholders = getAnswerPlaceholders();
-    final List<AnswerPlaceholder> thatAnswerPlaceholders = that.getAnswerPlaceholders();
-    if (answerPlaceholders.size() != thatAnswerPlaceholders.size()) return false;
-    for (int i = 0; i < answerPlaceholders.size(); i++) {
-      final AnswerPlaceholder placeholder = answerPlaceholders.get(i);
-      final AnswerPlaceholder thatPlaceholder = thatAnswerPlaceholders.get(i);
-      if (!placeholder.equals(thatPlaceholder)) return false;
-    }
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = getIndex();
-    result = 31 * result + name.hashCode();
+  public boolean hasFailedPlaceholders() {
     for (AnswerPlaceholder placeholder : myAnswerPlaceholders) {
-      result = 31 * result + placeholder.hashCode();
+      if (placeholder.getStatus() == StudyStatus.Failed) {
+        return true;
+      }
     }
-    return result;
+    return false;
   }
 }
index 2cff3f89318dd393cd925e29a2384b4f57363c1c..7daa0028d87b2ed892ec62fd249d7be78a3afad3 100644 (file)
@@ -26,6 +26,7 @@ import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
 import com.intellij.util.containers.ContainerUtil;
 import com.jetbrains.edu.learning.StudyProjectComponent;
+import com.jetbrains.edu.learning.StudySerializationUtils;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduNames;
@@ -132,7 +133,7 @@ public class StudyProjectGenerator {
     Reader reader = null;
     try {
       reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8");
-      Gson gson = new GsonBuilder().create();
+      Gson gson = new GsonBuilder().registerTypeAdapter(Course.class, new StudySerializationUtils.Json.CourseTypeAdapter(courseFile)).create();
       final Course course = gson.fromJson(reader, Course.class);
       course.initCourse(isAdaptive);
       return course;
index 8a9c17d0ef24b6bd891cd3dc446fe40c4fd3bc98..d640110af40462bcb729041e510ac6ac3b89ef46 100644 (file)
@@ -42,11 +42,11 @@ public class StudyEditorFactoryListener implements EditorFactoryListener {
       final Editor editor = e.getEditor();
       final Point point = e.getMouseEvent().getPoint();
       final LogicalPosition pos = editor.xyToLogicalPosition(point);
-      final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.getDocument(), pos);
+      final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.logicalPositionToOffset(pos));
       if (answerPlaceholder == null || answerPlaceholder.getSelected()) {
         return;
       }
-      int startOffset = answerPlaceholder.getRealStartOffset(editor.getDocument());
+      int startOffset = answerPlaceholder.getOffset();
       editor.getSelectionModel().setSelection(startOffset, startOffset + answerPlaceholder.getRealLength());
       answerPlaceholder.setSelected(true);
     }
index b54809ef9323bfc55c4df2f5f3e125cab3b41117..75b2c7709001bd0952b658b3e618b9d772d068a1 100644 (file)
@@ -1,11 +1,9 @@
 package com.jetbrains.edu.learning.navigation;
 
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.project.Project;
-import com.jetbrains.edu.learning.core.EduNames;
-import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.courseFormat.*;
 import org.jetbrains.annotations.NotNull;
 
@@ -67,29 +65,26 @@ public class StudyNavigator {
     final Project project = editor.getProject();
     if (project == null) return;
     for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) {
-      final StudyStatus status = StudyTaskManager.getInstance(project).getStatus(answerPlaceholder);
-      if (status != StudyStatus.Failed) {
+      if (answerPlaceholder.getStatus() != StudyStatus.Failed) {
         continue;
       }
-      navigateToAnswerPlaceholder(editor, answerPlaceholder, taskFile);
+      navigateToAnswerPlaceholder(editor, answerPlaceholder);
       break;
     }
   }
 
-  public static  void navigateToAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder,
-                                                  @NotNull final TaskFile taskFile) {
+  public static  void navigateToAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder) {
     if (editor.isDisposed() || !answerPlaceholder.isValid(editor.getDocument())) {
       return;
     }
-    LogicalPosition placeholderStart = new LogicalPosition(answerPlaceholder.getLine(), answerPlaceholder.getStart());
-    editor.getCaretModel().moveToLogicalPosition(placeholderStart);
+    editor.getCaretModel().moveToOffset(answerPlaceholder.getOffset());
   }
 
 
   public static  void navigateToFirstAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) {
     if (!taskFile.getAnswerPlaceholders().isEmpty()) {
       AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getAnswerPlaceholders());
-      navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder, taskFile);
+      navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder);
     }
   }
 
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Lesson.java
deleted file mode 100644 (file)
index bdf1f45..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class Lesson {
-  public String name;
-  public List<Task> taskList = new ArrayList<Task>();
-  public int myIndex = -1;
-  public LessonInfo myLessonInfo = new LessonInfo();
-
-}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/LessonInfo.java
deleted file mode 100644 (file)
index b19031b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-/**
- * Implementation of class which contains information about student progress in current lesson
- */
-public class LessonInfo {
-  public int myTaskNum;
-  public int myTaskFailed;
-  public int myTaskSolved;
-  public int myTaskUnchecked;
-
-}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/OldCourse.java
deleted file mode 100644 (file)
index fe25435..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class OldCourse {
-
-  public List<Lesson> lessons = new ArrayList<Lesson>();
-  public String description;
-  public String name;
-  public String myResourcePath = "";
-  public String author;
-  public boolean myUpToDate = false;
-
-}
\ No newline at end of file
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/Task.java
deleted file mode 100644 (file)
index 43a41c8..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Implementation of task which contains task files, tests, input file for tests
- */
-public class Task {
-  public String name;
-  public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
-  public int myIndex;
-}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskFile.java
deleted file mode 100644 (file)
index be2c082..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-import com.intellij.util.xmlb.annotations.Transient;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TaskFile {
-  public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>();
-  @Transient
-  public int myIndex = -1;
-}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/oldCourseFormat/TaskWindow.java
deleted file mode 100644 (file)
index 50ac639..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.jetbrains.edu.learning.oldCourseFormat;
-
-import com.jetbrains.edu.learning.courseFormat.StudyStatus;
-
-/**
- * Implementation of windows which user should type in
- */
-
-
-public class TaskWindow {
-  public int line = 0;
-  public int start = 0;
-  public String hint = "";
-  public String possibleAnswer = "";
-  public int length = 0;
-  public int myIndex = -1;
-  public int myInitialLine = -1;
-  public int myInitialStart = -1;
-  public int myInitialLength = -1;
-  public StudyStatus myStatus = StudyStatus.Unchecked;
-}
\ No newline at end of file
index 6d71a8276b7f2746e4b4db009c2ccfae54a90a83..c9821a7041120d75e83b9ea68c61ebfe4e583382 100644 (file)
@@ -43,7 +43,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
     if (course == null) {
       return;
     }
-    if (valueName.equals(myProject.getName())) {
+    if (valueName.equals(myProject.getBaseDir().getName())) {
       data.clearText();
       data.setIcon(InteractiveLearningIcons.Course);
       data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK));
@@ -89,9 +89,8 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
     return name.contains(EduNames.SANDBOX_DIR) ? 0 : 3;
   }
 
-  private void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) {
-    StudyStatus taskStatus = StudyTaskManager.getInstance(myProject).getStatus(lesson);
-    switch (taskStatus) {
+  private static void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) {
+    switch (lesson.getStatus()) {
       case Unchecked: {
         updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Lesson);
         break;
@@ -107,7 +106,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
   }
 
   protected void setStudyAttributes(Task task, PresentationData data, String additionalName) {
-    StudyStatus taskStatus = StudyTaskManager.getInstance(myProject).getStatus(task);
+    StudyStatus taskStatus = task.getStatus();
     switch (taskStatus) {
       case Unchecked: {
         updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Task);
index 50f6c4119e8e4d1bf47b65be231bf27e3309c90d..004b313054a1b44be2fe11909a72e1061d6cd55f 100644 (file)
@@ -81,8 +81,8 @@ public class CourseInfo {
   public void setAuthors(List<StepicUser> authors) {
     myAuthors = authors;
     for (StepicUser author : authors) {
-      if (author.id > 0) {
-        instructors.add(author.id);
+      if (author.getId() > 0) {
+        instructors.add(author.getId());
       }
     }
   }
index b527720d4af3213148d9c38ce2ce8b28af5f5e2b..642b0a160b3b12d9beb96b068a8d6ebb8eca1633 100644 (file)
@@ -170,7 +170,7 @@ public class EduAdaptiveStepicConnector {
 
       final boolean recommendationReaction =
         user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id),
-                                                   String.valueOf(user.id), reaction);
+                                                   String.valueOf(user.getId()), reaction);
       if (recommendationReaction) {
         final Task task = getNextRecommendation(project, course);
 
index a335fc48b768cf765f45dac240cb76bd5449217d..30429ed8e0610db7da09c5b9a72279f3c62bbe3e 100644 (file)
@@ -1,8 +1,6 @@
 package com.jetbrains.edu.learning.stepic;
 
-import com.google.gson.FieldNamingPolicy;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
+import com.google.gson.*;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.diagnostic.Logger;
@@ -16,6 +14,7 @@ import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileFilter;
 import com.intellij.util.net.ssl.CertificateManager;
+import com.jetbrains.edu.learning.StudySerializationUtils;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.core.EduNames;
 import com.jetbrains.edu.learning.core.EduUtils;
@@ -235,7 +234,7 @@ public class EduStepicConnector {
     if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
       throw new IOException("Stepic returned non 200 status code " + responseString);
     }
-    Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+    Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
     return gson.fromJson(responseString, container);
   }
   
@@ -718,6 +717,7 @@ public class EduStepicConnector {
   public static void postTask(final Project project, @NotNull final Task task, final int lessonId) {
     final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES);
     setHeaders(request, "application/json");
+    //TODO: register type adapter for task files here?
     final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
     ApplicationManager.getApplication().invokeLater(() -> {
       final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId));
index 04f43de30d37b83815e467658c261b4c1e7cf1f8..2196f9563d09fdfdc5745cdb993a2850bca6bf7f 100644 (file)
@@ -10,16 +10,16 @@ import com.jetbrains.edu.learning.StudyTaskManager;
 public class StepicUser {
   private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
   private static final Logger LOG = Logger.getInstance(StepicUser.class);
-  int id;
-  String firstName;
-  String lastName;
-  String email;
+  private int id = -1;
+  private String myFirstName = "";
+  private String myLastName = "";
+  private String myEmail = "";
 
   public StepicUser() {
   }
   
   public StepicUser(String email, String password) {
-    this.email = email;
+    this.myEmail = email;
     setPassword(password);
   }
 
@@ -32,27 +32,27 @@ public class StepicUser {
   }
 
   public String getFirstName() {
-    return firstName;
+    return myFirstName;
   }
 
   public void setFirstName(String firstName) {
-    this.firstName = firstName;
+    this.myFirstName = firstName;
   }
 
   public String getLastName() {
-    return lastName;
+    return myLastName;
   }
 
   public void setLastName(String last_name) {
-    this.lastName = last_name;
+    this.myLastName = last_name;
   }
 
   public String getEmail() {
-    return email;
+    return myEmail;
   }
 
   public void setEmail(String email) {
-    this.email = email;
+    this.myEmail = email;
   }
 
   @Transient
@@ -83,6 +83,6 @@ public class StepicUser {
   }
 
   public String getName() {
-    return StringUtil.join(new String[]{firstName, lastName}, " ");
+    return StringUtil.join(new String[]{myFirstName, myLastName}, " ");
   }
 }
index eea24f44ca0d287ab494121d2fb23f5a78667c42..d57ad24c70f83e24f15d97b9d50fd12322807105 100644 (file)
@@ -43,7 +43,7 @@ public class StudyProgressToolWindowFactory implements ToolWindowFactory, DumbAw
       for (Lesson lesson : lessons) {
         if (lesson.getName().equals(EduNames.PYCHARM_ADDITIONAL)) continue;
         taskNum += lesson.getTaskList().size();
-        taskSolved += getSolvedTasks(lesson, taskManager);
+        taskSolved += getSolvedTasks(lesson);
       }
       String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum);
 
@@ -68,10 +68,10 @@ public class StudyProgressToolWindowFactory implements ToolWindowFactory, DumbAw
     contentPanel.add(statisticLabel);
   }
 
-  private static int getSolvedTasks(@NotNull final Lesson lesson, StudyTaskManager taskManager) {
+  private static int getSolvedTasks(@NotNull final Lesson lesson) {
     int solved = 0;
     for (Task task : lesson.getTaskList()) {
-      if (taskManager.getStatus(task) == StudyStatus.Solved) {
+      if (task.getStatus() == StudyStatus.Solved) {
         solved += 1;
       }
     }
index 16bbdf9f218be85ae68c7ee24d32ff2f55592bbc..b3252a761426b1d3ef0232975c5cb6dbeeae4f86 100644 (file)
@@ -95,12 +95,11 @@ public class PyStudyCheckAction extends StudyCheckAction {
       protected void onTaskFailed(String message) {
         ApplicationManager.getApplication().invokeLater(() -> {
           if (myTaskDir == null) return;
-          myTaskManger.setStatus(myTask, StudyStatus.Failed);
+          myTask.setStatus(StudyStatus.Failed);
           for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
             final String name = entry.getKey();
             final TaskFile taskFile = entry.getValue();
             if (taskFile.getAnswerPlaceholders().size() < 2) {
-              myTaskManger.setStatus(taskFile, StudyStatus.Failed);
               continue;
             }
             final Course course = myTaskManger.getCourse();