4fb594bb89a3ad0d25ae9299c95ef5a3b47ff7a5
[idea/community.git] / python / ipnb / src / org / jetbrains / plugins / ipnb / format / IpnbParser.java
1 package org.jetbrains.plugins.ipnb.format;
2
3 import com.google.common.collect.Lists;
4 import com.google.gson.*;
5 import com.google.gson.annotations.SerializedName;
6 import com.google.gson.stream.JsonWriter;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.editor.Document;
9 import com.intellij.openapi.module.Module;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.project.ProjectUtil;
12 import com.intellij.openapi.projectRoots.Sdk;
13 import com.intellij.openapi.roots.ProjectRootManager;
14 import com.intellij.openapi.ui.Messages;
15 import com.intellij.openapi.util.text.StringUtil;
16 import com.intellij.openapi.vfs.VirtualFile;
17 import com.intellij.util.text.VersionComparatorUtil;
18 import com.jetbrains.python.packaging.PyPackage;
19 import com.jetbrains.python.packaging.PyPackageUtil;
20 import com.jetbrains.python.sdk.PythonSdkType;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23 import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel;
24 import org.jetbrains.plugins.ipnb.editor.panels.IpnbFilePanel;
25 import org.jetbrains.plugins.ipnb.format.cells.*;
26 import org.jetbrains.plugins.ipnb.format.cells.output.*;
27
28 import java.io.*;
29 import java.lang.reflect.Type;
30 import java.nio.charset.Charset;
31 import java.util.*;
32
33 public class IpnbParser {
34   private static final Logger LOG = Logger.getInstance(IpnbParser.class);
35   private static final Gson gson = initGson();
36   private static final List<String> myErrors = new ArrayList<>();
37   private static final String VALIDATION_ERROR_TEXT = "An invalid notebook may not function properly. The validation error was:";
38   private static final String VALIDATION_ERROR_TITLE = "Notebook Validation Failed";
39
40   @NotNull
41   private static Gson initGson() {
42     final GsonBuilder builder =
43       new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(IpnbCellRaw.class, new RawCellAdapter())
44         .registerTypeAdapter(IpnbFileRaw.class, new FileAdapter()).registerTypeAdapter(CellOutputRaw.class, new OutputsAdapter())
45         .registerTypeAdapter(OutputDataRaw.class, new OutputDataAdapter())
46         .registerTypeAdapter(CellOutputRaw.class, new CellOutputDeserializer())
47         .registerTypeAdapter(OutputDataRaw.class, new OutputDataDeserializer())
48         .registerTypeAdapter(IpnbCellRaw.class, new CellRawDeserializer()).serializeNulls();
49     return builder.create();
50   }
51
52   @NotNull
53   public static IpnbFile parseIpnbFile(@NotNull final CharSequence fileText, @NotNull final VirtualFile virtualFile) throws IOException {
54     myErrors.clear();
55     
56     final String path = virtualFile.getPath();
57     IpnbFileRaw rawFile = gson.fromJson(fileText.toString(), IpnbFileRaw.class);
58     if (rawFile == null) {
59       int nbformat = isIpythonNewFormat(virtualFile) ? 4 : 3;
60       return new IpnbFile(Collections.emptyMap(), nbformat, Lists.newArrayList(), path);
61     }
62     List<IpnbCell> cells = new ArrayList<>();
63     final IpnbWorksheet[] worksheets = rawFile.worksheets;
64     if (worksheets == null) {
65       for (IpnbCellRaw rawCell : rawFile.cells) {
66         cells.add(rawCell.createCell(validateSource(rawCell)));
67       }
68     }
69     else {
70       for (IpnbWorksheet worksheet : worksheets) {
71         final List<IpnbCellRaw> rawCells = worksheet.cells;
72         for (IpnbCellRaw rawCell : rawCells) {
73           cells.add(rawCell.createCell(validateSource(rawCell)));
74         }
75       }
76     }
77     showValidationMessage();
78     return new IpnbFile(rawFile.metadata, rawFile.nbformat, cells, path);
79   }
80
81   private static boolean validateSource(IpnbCellRaw cell) {
82     if (cell.source == null) {
83       myErrors.add(VALIDATION_ERROR_TEXT + "\n" + "\"source\" is required property:\n" + cell);
84       return false;
85     }
86     return true;
87   }
88   
89   private static void showValidationMessage() {
90     if (!myErrors.isEmpty()) {
91       Messages.showWarningDialog(myErrors.get(0), VALIDATION_ERROR_TITLE);
92     }
93   }
94
95   public static boolean isIpythonNewFormat(@NotNull final VirtualFile virtualFile) {
96     final Project project = ProjectUtil.guessProjectForFile(virtualFile);
97     if (project != null) {
98       final Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(virtualFile);
99       if (module != null) {
100         final Sdk sdk = PythonSdkType.findPythonSdk(module);
101         if (sdk != null) {
102           // It should be called first before IpnbConnectionManager#startIpythonServer()
103           final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
104           final PyPackage ipython = packages != null ? PyPackageUtil.findPackage(packages, "ipython") : null;
105           final PyPackage jupyter = packages != null ? PyPackageUtil.findPackage(packages, "jupyter") : null;
106           if (jupyter == null && ipython != null && VersionComparatorUtil.compare(ipython.getVersion(), "3.0") <= 0) {
107             return false;
108           }
109         }
110       }
111     }
112     return true;
113   }
114
115   @NotNull
116   public static IpnbFile parseIpnbFile(@NotNull Document document, @NotNull final VirtualFile virtualFile) throws IOException {
117     return parseIpnbFile(document.getImmutableCharSequence(), virtualFile);
118   }
119
120   public static void saveIpnbFile(@NotNull final IpnbFilePanel ipnbPanel) {
121     final String json = newDocumentText(ipnbPanel);
122     if (json == null) return;
123     writeToFile(ipnbPanel.getIpnbFile().getPath(), json);
124   }
125
126   @Nullable
127   public static String newDocumentText(@NotNull final IpnbFilePanel ipnbPanel) {
128     final IpnbFile ipnbFile = ipnbPanel.getIpnbFile();
129     if (ipnbFile == null) return null;
130     for (IpnbEditablePanel panel : ipnbPanel.getIpnbPanels()) {
131       if (panel.isModified()) {
132         panel.updateCellSource();
133       }
134     }
135
136     final IpnbFileRaw fileRaw = new IpnbFileRaw();
137     fileRaw.metadata = ipnbFile.getMetadata();
138     if (ipnbFile.getNbformat() == 4) {
139       for (IpnbCell cell: ipnbFile.getCells()) {
140         fileRaw.cells.add(IpnbCellRaw.fromCell(cell, ipnbFile.getNbformat()));
141       }
142     }
143     else {
144       final IpnbWorksheet worksheet = new IpnbWorksheet();
145       worksheet.cells.clear();
146       for (IpnbCell cell : ipnbFile.getCells()) {
147         worksheet.cells.add(IpnbCellRaw.fromCell(cell, ipnbFile.getNbformat()));
148       }
149       fileRaw.worksheets = new IpnbWorksheet[]{worksheet};
150     }
151     final StringWriter stringWriter = new StringWriter();
152     final JsonWriter writer = new JsonWriter(stringWriter);
153     writer.setIndent(" ");
154     gson.toJson(fileRaw, fileRaw.getClass(), writer);
155     return stringWriter.toString();
156   }
157
158   private static void writeToFile(@NotNull final String path, @NotNull final String json) {
159     final File file = new File(path);
160     try {
161       final FileOutputStream fileOutputStream = new FileOutputStream(file);
162       final OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, Charset.forName("UTF-8").newEncoder());
163       try {
164         writer.write(json);
165       } catch (IOException e) {
166         LOG.error(e);
167       }
168       finally {
169         try {
170           writer.close();
171           fileOutputStream.close();
172         } catch (IOException e) {
173           LOG.error(e);
174         }
175       }
176     }
177     catch (FileNotFoundException e) {
178       LOG.error(e);
179     }
180   }
181
182   @SuppressWarnings("unused")
183   public static class IpnbFileRaw {
184     IpnbWorksheet[] worksheets;
185     List<IpnbCellRaw> cells = new ArrayList<>();
186     Map<String, Object> metadata = new HashMap<>();
187     int nbformat = 4;
188     int nbformat_minor;
189   }
190
191   private static class IpnbWorksheet {
192     List<IpnbCellRaw> cells = new ArrayList<>();
193   }
194
195   @SuppressWarnings("unused")
196   private static class IpnbCellRaw {
197     String cell_type;
198     Integer execution_count;
199     Map<String, Object> metadata = new HashMap<>();
200     Integer level;
201     List<CellOutputRaw> outputs;
202     List<String> source;
203     List<String> input;
204     String language;
205     Integer prompt_number;
206
207     @Override
208     public String toString() {
209       return new GsonBuilder().setPrettyPrinting().create().toJson(this);
210     }
211
212     public static IpnbCellRaw fromCell(@NotNull final IpnbCell cell, int nbformat) {
213       final IpnbCellRaw raw = new IpnbCellRaw();
214       if (cell instanceof IpnbEditableCell) {
215         raw.metadata = ((IpnbEditableCell)cell).getMetadata();
216       }
217       if (cell instanceof IpnbMarkdownCell) {
218         raw.cell_type = "markdown";
219         raw.source = ((IpnbMarkdownCell)cell).getSource();
220       }
221       else if (cell instanceof IpnbCodeCell) {
222         raw.cell_type = "code";
223         final ArrayList<CellOutputRaw> outputRaws = new ArrayList<>();
224         for (IpnbOutputCell outputCell : ((IpnbCodeCell)cell).getCellOutputs()) {
225           outputRaws.add(CellOutputRaw.fromOutput(outputCell, nbformat));
226         }
227         raw.outputs = outputRaws;
228         final Integer promptNumber = ((IpnbCodeCell)cell).getPromptNumber();
229         if (nbformat == 4) {
230           raw.execution_count = promptNumber != null && promptNumber >= 0 ? promptNumber : null;
231           raw.source = ((IpnbCodeCell)cell).getSource();
232         }
233         else {
234           raw.prompt_number = promptNumber != null && promptNumber >= 0 ? promptNumber : null;
235           raw.language = ((IpnbCodeCell)cell).getLanguage();
236           raw.input = ((IpnbCodeCell)cell).getSource();
237         }
238       }
239       else if (cell instanceof IpnbRawCell) {
240         raw.cell_type = "raw";
241         raw.source = ((IpnbRawCell)cell).getSource();
242       }
243       else if (cell instanceof IpnbHeadingCell) {
244         raw.cell_type = "heading";
245         raw.source = ((IpnbHeadingCell)cell).getSource();
246         raw.level = ((IpnbHeadingCell)cell).getLevel();
247       }
248       return raw;
249     }
250
251     @Nullable
252     public IpnbCell createCell(boolean isValidSource) {
253       final IpnbCell cell;
254       if (cell_type.equals("markdown")) {
255         cell = new IpnbMarkdownCell(isValidSource ? source : Collections.emptyList(), metadata);
256       }
257       else if (cell_type.equals("code")) {
258         final List<IpnbOutputCell> outputCells = new ArrayList<>();
259         for (CellOutputRaw outputRaw : outputs) {
260           outputCells.add(outputRaw.createOutput());
261         }
262         final Integer prompt = prompt_number != null ? prompt_number : execution_count;
263         cell = new IpnbCodeCell(language == null ? "python" : language, 
264                                 input == null ?  (isValidSource ? source : Collections.emptyList()) : input, 
265                                 prompt, outputCells, metadata);
266         ;
267       }
268       else if (cell_type.equals("raw")) {
269         cell = new IpnbRawCell(isValidSource ? source : Collections.emptyList());
270       }
271       else if (cell_type.equals("heading")) {
272         cell = new IpnbHeadingCell(isValidSource ? source : Collections.emptyList(), level, metadata);
273       }
274       else {
275         cell = null;
276       }
277       return cell;
278     }
279   }
280
281   private static class CellOutputRaw {
282     String ename;
283     String name;
284     String evalue;
285     OutputDataRaw data;
286     Integer execution_count;
287     String png;
288     String stream;
289     String jpeg;
290     List<String> html;
291     List<String> latex;
292     List<String> svg;
293     Integer prompt_number;
294     List<String> traceback;
295     Map<String, Object> metadata;
296     String output_type;
297     List<String> text;
298
299     public static CellOutputRaw fromOutput(@NotNull final IpnbOutputCell outputCell, int nbformat) {
300       final CellOutputRaw raw = new CellOutputRaw();
301       raw.metadata = outputCell.getMetadata();
302       if (raw.metadata == null && !(outputCell instanceof IpnbStreamOutputCell) && !(outputCell instanceof IpnbErrorOutputCell)) {
303         raw.metadata = Collections.emptyMap();
304       }
305
306       if (outputCell instanceof IpnbPngOutputCell) {
307         if (nbformat == 4) {
308           final OutputDataRaw dataRaw = new OutputDataRaw();
309           dataRaw.png = ((IpnbPngOutputCell)outputCell).getBase64String();
310           dataRaw.text = outputCell.getText();
311           raw.data = dataRaw;
312           raw.execution_count = outputCell.getPromptNumber();
313           raw.output_type = outputCell.getPromptNumber() != null ? "execute_result" : "display_data";
314         }
315         else {
316           raw.png = ((IpnbPngOutputCell)outputCell).getBase64String();
317           raw.text = outputCell.getText();
318         }
319       }
320       else if (outputCell instanceof IpnbSvgOutputCell) {
321         if (nbformat == 4) {
322           final OutputDataRaw dataRaw = new OutputDataRaw();
323           dataRaw.text = outputCell.getText();
324           dataRaw.svg = ((IpnbSvgOutputCell)outputCell).getSvg();
325           raw.data = dataRaw;
326           raw.execution_count = outputCell.getPromptNumber();
327           raw.output_type = outputCell.getPromptNumber() != null ? "execute_result" : "display_data";
328         }
329         else {
330           raw.svg = ((IpnbSvgOutputCell)outputCell).getSvg();
331           raw.text = outputCell.getText();
332         }
333       }
334       else if (outputCell instanceof IpnbJpegOutputCell) {
335         if (nbformat == 4) {
336           final OutputDataRaw dataRaw = new OutputDataRaw();
337           dataRaw.text = outputCell.getText();
338           dataRaw.jpeg = Lists.newArrayList(((IpnbJpegOutputCell)outputCell).getBase64String());
339           raw.data = dataRaw;
340         }
341         else {
342           raw.jpeg = ((IpnbJpegOutputCell)outputCell).getBase64String();
343           raw.text = outputCell.getText();
344         }
345       }
346       else if (outputCell instanceof IpnbLatexOutputCell) {
347         if (nbformat == 4) {
348           final OutputDataRaw dataRaw = new OutputDataRaw();
349           dataRaw.text = outputCell.getText();
350           dataRaw.latex = ((IpnbLatexOutputCell)outputCell).getLatex();
351           raw.data = dataRaw;
352           raw.execution_count = outputCell.getPromptNumber();
353           raw.output_type = "execute_result";
354         }
355         else {
356           raw.latex = ((IpnbLatexOutputCell)outputCell).getLatex();
357           raw.text = outputCell.getText();
358           raw.prompt_number = outputCell.getPromptNumber();
359         }
360       }
361       else if (outputCell instanceof IpnbStreamOutputCell) {
362         if (nbformat == 4) {
363           raw.name = ((IpnbStreamOutputCell)outputCell).getStream();
364         }
365         else {
366           raw.stream = ((IpnbStreamOutputCell)outputCell).getStream();
367         }
368         raw.text = outputCell.getText();
369         raw.output_type = "stream";
370       }
371       else if (outputCell instanceof IpnbHtmlOutputCell) {
372         if (nbformat == 4) {
373           final OutputDataRaw dataRaw = new OutputDataRaw();
374           dataRaw.html = ((IpnbHtmlOutputCell)outputCell).getHtmls();
375           dataRaw.text = outputCell.getText();
376           raw.data = dataRaw;
377           raw.execution_count = outputCell.getPromptNumber();
378         }
379         else {
380           raw.html = ((IpnbHtmlOutputCell)outputCell).getHtmls();
381         }
382         raw.output_type = nbformat == 4 ? "execute_result" : "pyout";
383       }
384       else if (outputCell instanceof IpnbErrorOutputCell) {
385         raw.output_type = nbformat == 4 ? "error" : "pyerr";
386         raw.evalue = ((IpnbErrorOutputCell)outputCell).getEvalue();
387         raw.ename = ((IpnbErrorOutputCell)outputCell).getEname();
388         raw.traceback = outputCell.getText();
389       }
390       else if (outputCell instanceof IpnbOutOutputCell) {
391         if (nbformat == 4) {
392           raw.execution_count = outputCell.getPromptNumber();
393           raw.output_type = "execute_result";
394           final OutputDataRaw dataRaw = new OutputDataRaw();
395           dataRaw.text = outputCell.getText();
396           raw.data = dataRaw;
397         }
398         else {
399           raw.output_type = "pyout";
400           raw.prompt_number = outputCell.getPromptNumber();
401           raw.text = outputCell.getText();
402         }
403       }
404       else {
405         raw.text = outputCell.getText();
406       }
407       return raw;
408     }
409
410     public IpnbOutputCell createOutput() {
411       List<String> text = this.text != null ? this.text : data != null ? data.text : Lists.newArrayList();
412       Integer prompt = execution_count != null ? execution_count : prompt_number;
413       final IpnbOutputCell outputCell;
414       if (png != null || (data != null && data.png != null)) {
415         outputCell = new IpnbPngOutputCell(png == null ? StringUtil.join(data.png) : png, text, prompt, metadata);
416       }
417       else if (jpeg != null || (data != null && data.jpeg != null)) {
418         outputCell = new IpnbJpegOutputCell(jpeg == null ? StringUtil.join(data.jpeg, "") : jpeg, text, prompt, metadata);
419       }
420       else if (svg != null || (data != null && data.svg != null)) {
421         outputCell = new IpnbSvgOutputCell(svg == null ? data.svg : svg, text, prompt, metadata);
422       }
423       else if (html != null || (data != null && data.html != null)) {
424         outputCell = new IpnbHtmlOutputCell(html == null ? data.html : html, text, prompt, metadata);
425       }
426       else if (latex != null || (data != null && data.latex != null)) {
427         outputCell = new IpnbLatexOutputCell(latex == null ? data.latex : latex, prompt, text, metadata);
428       }
429       else if (stream != null || name != null) {
430         outputCell = new IpnbStreamOutputCell(stream == null ? name : stream, text, prompt, metadata);
431       }
432       else if ("pyerr".equals(output_type) || "error".equals(output_type)) {
433         outputCell = new IpnbErrorOutputCell(evalue, ename, traceback, prompt, metadata);
434       }
435       else if ("pyout".equals(output_type)) {
436         outputCell = new IpnbOutOutputCell(text, prompt, metadata);
437       }
438       else if ("execute_result".equals(output_type) && data != null) {
439         outputCell = new IpnbOutOutputCell(data.text, prompt, metadata);
440       }
441       else if ("display_data".equals(output_type)){
442         outputCell = new IpnbPngOutputCell(null, text, prompt, metadata);
443       }
444       else {
445         outputCell = new IpnbOutputCell(text, prompt, metadata);
446       }
447       return outputCell;
448     }
449   }
450
451   private static class OutputDataRaw {
452     @SerializedName("image/png") String png;
453     @SerializedName("text/html") List<String> html;
454     @SerializedName("image/svg+xml") List<String> svg;
455     @SerializedName("image/jpeg") List<String> jpeg;
456     @SerializedName("text/latex") List<String> latex;
457     @SerializedName("text/plain") List<String> text;
458   }
459
460   static class RawCellAdapter implements JsonSerializer<IpnbCellRaw> {
461     @Override
462     public JsonElement serialize(IpnbCellRaw cellRaw, Type typeOfSrc, JsonSerializationContext context) {
463       final JsonObject jsonObject = new JsonObject();
464       jsonObject.addProperty("cell_type", cellRaw.cell_type);
465       if ("code".equals(cellRaw.cell_type)) {
466         final Integer count = cellRaw.execution_count;
467         if (count == null) {
468           jsonObject.add("execution_count", JsonNull.INSTANCE);
469         }
470         else {
471           jsonObject.addProperty("execution_count", count);
472         }
473       }
474       if (cellRaw.metadata != null) {
475         final JsonElement metadata = gson.toJsonTree(cellRaw.metadata);
476         jsonObject.add("metadata", metadata);
477       }
478       if (cellRaw.level != null) {
479         jsonObject.addProperty("level", cellRaw.level);
480       }
481
482       if (cellRaw.outputs != null) {
483         final JsonElement outputs = gson.toJsonTree(cellRaw.outputs);
484         jsonObject.add("outputs", outputs);
485       }
486       if (cellRaw.source != null) {
487         final JsonElement source = gson.toJsonTree(cellRaw.source);
488         jsonObject.add("source", source);
489       }
490       if (cellRaw.input != null) {
491         final JsonElement input = gson.toJsonTree(cellRaw.input);
492         jsonObject.add("input", input);
493       }
494       if (cellRaw.language != null) {
495         jsonObject.addProperty("language", cellRaw.language);
496       }
497       if (cellRaw.prompt_number != null) {
498         jsonObject.addProperty("prompt_number", cellRaw.prompt_number);
499       }
500
501       return jsonObject;
502     }
503   }
504   static class FileAdapter implements JsonSerializer<IpnbFileRaw> {
505     @Override
506     public JsonElement serialize(IpnbFileRaw fileRaw, Type typeOfSrc, JsonSerializationContext context) {
507       final JsonObject jsonObject = new JsonObject();
508       if (fileRaw.worksheets != null) {
509         final JsonElement worksheets = gson.toJsonTree(fileRaw.worksheets);
510         jsonObject.add("worksheets", worksheets);
511       }
512       if (fileRaw.cells != null) {
513         final JsonElement cells = gson.toJsonTree(fileRaw.cells);
514         jsonObject.add("cells", cells);
515       }
516       final JsonElement metadata = gson.toJsonTree(fileRaw.metadata);
517       jsonObject.add("metadata", metadata);
518
519       jsonObject.addProperty("nbformat", fileRaw.nbformat);
520       jsonObject.addProperty("nbformat_minor", fileRaw.nbformat_minor);
521
522       return jsonObject;
523     }
524   }
525
526   static class CellRawDeserializer implements JsonDeserializer<IpnbCellRaw> {
527
528     @Override
529     public IpnbCellRaw deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
530       throws JsonParseException {
531       final JsonObject object = json.getAsJsonObject();
532       final IpnbCellRaw cellRaw = new IpnbCellRaw();
533       final JsonElement cell_type = object.get("cell_type");
534       if (cell_type != null) {
535         cellRaw.cell_type = cell_type.getAsString();
536       }
537       final JsonElement count = object.get("execution_count");
538       if (count != null) {
539         cellRaw.execution_count = count.isJsonNull() ? null : count.getAsInt();
540       }
541       final JsonElement metadata = object.get("metadata");
542       if (metadata != null) {
543         cellRaw.metadata = gson.fromJson(metadata, Map.class);
544       }
545       final JsonElement level = object.get("level");
546       if (level != null) {
547         cellRaw.level = level.getAsInt();
548       }
549
550       final JsonElement outputsElement = object.get("outputs");
551       if (outputsElement != null) {
552         final JsonArray outputs = outputsElement.getAsJsonArray();
553         cellRaw.outputs = Lists.newArrayList();
554         for (JsonElement output : outputs) {
555           cellRaw.outputs.add(gson.fromJson(output, CellOutputRaw.class));
556         }
557       }
558       cellRaw.source = getStringOrArray("source", object);
559       cellRaw.input = getStringOrArray("input", object);
560       final JsonElement language = object.get("language");
561       if (language != null) {
562         cellRaw.language = language.getAsString();
563       }
564       final JsonElement number = object.get("prompt_number");
565       if (number != null) {
566         cellRaw.prompt_number = number.getAsInt();
567       }
568       return cellRaw;
569     }
570   }
571
572   static class OutputDataDeserializer implements JsonDeserializer<OutputDataRaw> {
573
574     @Override
575     public OutputDataRaw deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
576       throws JsonParseException {
577       final JsonObject object = json.getAsJsonObject();
578       final OutputDataRaw dataRaw = new OutputDataRaw();
579       final JsonElement png = object.get("image/png");
580       if (png != null) {
581         dataRaw.png = png.getAsString();
582       }
583       dataRaw.html = getStringOrArray("text/html", object);
584       dataRaw.svg = getStringOrArray("image/svg+xml", object);
585       dataRaw.jpeg = getStringOrArray("image/jpeg", object);
586       dataRaw.latex = getStringOrArray("text/latex", object);
587       dataRaw.text = getStringOrArray("text/plain", object);
588       return dataRaw;
589     }
590   }
591   static class CellOutputDeserializer implements JsonDeserializer<CellOutputRaw> {
592
593     @Override
594     public CellOutputRaw deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
595       throws JsonParseException {
596       final JsonObject object = json.getAsJsonObject();
597       final CellOutputRaw cellOutputRaw = new CellOutputRaw();
598       final JsonElement ename = object.get("ename");
599       if (ename != null) {
600         cellOutputRaw.ename = ename.getAsString();
601       }
602       final JsonElement name = object.get("name");
603       if (name != null) {
604         cellOutputRaw.name = name.getAsString();
605       }
606       final JsonElement evalue = object.get("evalue");
607       if (evalue != null) {
608         cellOutputRaw.evalue = evalue.getAsString();
609       }
610       final JsonElement data = object.get("data");
611       if (data != null) {
612         cellOutputRaw.data = gson.fromJson(data, OutputDataRaw.class);
613       }
614
615       final JsonElement count = object.get("execution_count");
616       if (count != null) {
617         cellOutputRaw.execution_count = count.getAsInt();
618       }
619       final JsonElement outputType = object.get("output_type");
620       if (outputType != null) {
621         cellOutputRaw.output_type = outputType.getAsString();
622       }
623       final JsonElement png = object.get("png");
624       if (png != null) {
625         cellOutputRaw.png = png.getAsString();
626       }
627       final JsonElement stream = object.get("stream");
628       if (stream != null) {
629         cellOutputRaw.stream = stream.getAsString();
630       }
631       final JsonElement jpeg = object.get("jpeg");
632       if (jpeg != null) {
633         cellOutputRaw.jpeg = jpeg.getAsString();
634       }
635
636       cellOutputRaw.html = getStringOrArray("html", object);
637       cellOutputRaw.latex = getStringOrArray("latex", object);
638       cellOutputRaw.svg = getStringOrArray("svg", object);
639       final JsonElement promptNumber = object.get("prompt_number");
640       if (promptNumber != null) {
641         cellOutputRaw.prompt_number = promptNumber.getAsInt();
642       }
643       cellOutputRaw.text = getStringOrArray("text", object);
644       cellOutputRaw.traceback = getStringOrArray("traceback", object);
645       final JsonElement metadata = object.get("metadata");
646       if (metadata != null) {
647         cellOutputRaw.metadata = gson.fromJson(metadata, Map.class);
648       }
649
650       return cellOutputRaw;
651     }
652   }
653
654   @Nullable
655   private static ArrayList<String> getStringOrArray(String name, JsonObject object) {
656     final JsonElement jsonElement = object.get(name);
657     final ArrayList<String> strings = Lists.newArrayList();
658     if (jsonElement == null) return null;
659     if (jsonElement.isJsonArray()) {
660       final JsonArray array = jsonElement.getAsJsonArray();
661       for (JsonElement element : array) {
662         strings.add(element.getAsString());
663       }
664     }
665     else {
666       strings.add(jsonElement.getAsString());
667     }
668     return strings;
669   }
670
671   static class OutputsAdapter implements JsonSerializer<CellOutputRaw> {
672     @Override
673     public JsonElement serialize(CellOutputRaw cellRaw, Type typeOfSrc, JsonSerializationContext context) {
674       final JsonObject jsonObject = new JsonObject();
675       if (cellRaw.ename != null) {
676         jsonObject.addProperty("ename", cellRaw.ename);
677       }
678       if (cellRaw.name != null) {
679         jsonObject.addProperty("name", cellRaw.name);
680       }
681       if (cellRaw.evalue != null) {
682         jsonObject.addProperty("evalue", cellRaw.evalue);
683       }
684
685       if (cellRaw.data != null) {
686         final JsonElement data = gson.toJsonTree(cellRaw.data);
687         jsonObject.add("data", data);
688       }
689       if (cellRaw.execution_count != null) {
690         jsonObject.addProperty("execution_count", cellRaw.execution_count);
691       }
692       if (cellRaw.png != null) {
693         jsonObject.addProperty("png", cellRaw.png);
694       }
695
696       if (cellRaw.stream != null) {
697         jsonObject.addProperty("stream", cellRaw.stream);
698       }
699
700       if (cellRaw.jpeg != null) {
701         jsonObject.addProperty("jpeg", cellRaw.jpeg);
702       }
703
704       if (cellRaw.html != null) {
705         final JsonElement html = gson.toJsonTree(cellRaw.html);
706         jsonObject.add("html", html);
707       }
708       if (cellRaw.latex != null) {
709         final JsonElement latex = gson.toJsonTree(cellRaw.latex);
710         jsonObject.add("latex", latex);
711       }
712
713       if (cellRaw.svg != null) {
714         final JsonElement svg = gson.toJsonTree(cellRaw.svg);
715         jsonObject.add("svg", svg);
716       }
717       if (cellRaw.prompt_number != null) {
718         jsonObject.addProperty("prompt_number", cellRaw.prompt_number);
719       }
720       if (cellRaw.traceback != null) {
721         final JsonElement traceback = gson.toJsonTree(cellRaw.traceback);
722         jsonObject.add("traceback", traceback);
723       }
724
725       if (cellRaw.metadata != null) {
726         final JsonElement metadata = gson.toJsonTree(cellRaw.metadata);
727         jsonObject.add("metadata", metadata);
728       }
729       if (cellRaw.output_type != null) {
730         jsonObject.addProperty("output_type", cellRaw.output_type);
731       }
732       if (cellRaw.text != null) {
733         final JsonElement text = gson.toJsonTree(cellRaw.text);
734         jsonObject.add("text", text);
735       }
736
737       return jsonObject;
738     }
739   }
740
741   static class OutputDataAdapter implements JsonSerializer<OutputDataRaw> {
742     @Override
743     public JsonElement serialize(OutputDataRaw cellRaw, Type typeOfSrc, JsonSerializationContext context) {
744       final JsonObject jsonObject = new JsonObject();
745
746       if (cellRaw.png != null) {
747         jsonObject.addProperty("image/png", cellRaw.png);
748       }
749       if (cellRaw.html != null) {
750         final JsonElement html = gson.toJsonTree(cellRaw.html);
751         jsonObject.add("text/html", html);
752       }
753       if (cellRaw.svg != null) {
754         final JsonElement svg = gson.toJsonTree(cellRaw.svg);
755         jsonObject.add("image/svg+xml", svg);
756       }
757       if (cellRaw.jpeg != null) {
758         final JsonElement jpeg = gson.toJsonTree(cellRaw.jpeg);
759         jsonObject.add("image/jpeg", jpeg);
760       }
761       if (cellRaw.latex != null) {
762         final JsonElement latex = gson.toJsonTree(cellRaw.latex);
763         jsonObject.add("text/latex", latex);
764       }
765       if (cellRaw.text != null) {
766         final JsonElement text = gson.toJsonTree(cellRaw.text);
767         jsonObject.add("text/plain", text);
768       }
769
770       return jsonObject;
771     }
772   }
773 }