/* * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.example.spline.persistence; import android.content.Context; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.android.example.spline.model.Document; import com.android.example.spline.model.Layer; import com.android.example.spline.model.LayerGroup; import com.android.example.spline.model.OvalLayer; import com.android.example.spline.model.RectLayer; import com.android.example.spline.model.SelectionGroup; import com.android.example.spline.model.ShapeLayer; import com.android.example.spline.model.TriangleLayer; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.util.UUID; /** * A singleton class for loading and persisting documents to the local file system or cloud storage * as JSON flat files. */ public class DocumentRepository { private static final String TYPE = "type"; private static final String CURRENT_LAYER = "currentLayer"; private static final String CLIPBOARD_LAYER = "clipboardLayer"; private static final String LAYERS = "layers"; private static final String LAYER_GROUP = "LayerGroup"; private static final String SELECTION_GROUP = "SelectionGroup"; private static final String RECT_LAYER = "RectLayer"; private static final String OVAL_LAYER = "OvalLayer"; private static final String TRIANGLE_LAYER = "TriangleLayer"; private static DocumentRepository instance = null; private Gson gson; protected DocumentRepository() { GsonBuilder builder = new GsonBuilder(); DocumentTypeAdapter documentAdapter = new DocumentTypeAdapter(); builder.registerTypeAdapter(Document.class, documentAdapter); builder.setPrettyPrinting(); gson = builder.create(); } public static DocumentRepository getInstance() { if (instance == null) { instance = new DocumentRepository(); } return instance; } public void save(String filename, Document document, Context context) { try { FileOutputStream outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); String json = gson.toJson(document); outputStream.write(json.getBytes()); outputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public Document load(String filename, Context context) { String json = null; try { FileInputStream inputStream = context.openFileInput(filename); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } reader.close(); json = sb.toString(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return gson.fromJson(json, Document.class); } /** * Custom serialize/deserialize class for Document objects. Primary custom function is to handle * the serialization/deserialization of the currently selected layer with the layer's UUID or a * list of UUIDs of a SelectionGroup's layers to persist the current layer as a reference to a * layer in the layer tree, rather than it's own layer. */ private class DocumentTypeAdapter implements JsonSerializer<Document>, JsonDeserializer<Document> { private Gson g; public DocumentTypeAdapter() { GsonBuilder builder = new GsonBuilder(); LayerTypeAdapter layerAdapter = new LayerTypeAdapter(); builder.registerTypeAdapter(Layer.class, layerAdapter); builder.registerTypeAdapter(LayerGroup.class, layerAdapter); g = builder.create(); } @Override public JsonElement serialize(Document src, Type typeOfSrc, JsonSerializationContext context) { JsonObject obj = g.toJsonTree(src).getAsJsonObject(); if (src.getCurrentLayer() != null) { Layer current = src.getCurrentLayer(); // If the current selection was a selection group, serialize the group as an array // of ids, other add the single string id of the current layer if (current instanceof SelectionGroup) { SelectionGroup sg = (SelectionGroup) current; JsonArray jsonIds = new JsonArray(); for (Layer l : sg.getLayers()) { jsonIds.add(l.getId().toString()); } obj.add(CURRENT_LAYER, jsonIds); } else { obj.addProperty(CURRENT_LAYER, src.getCurrentLayer().getId().toString()); } } return obj; } @Override public Document deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { JsonObject obj = json.getAsJsonObject(); JsonElement currentLayerEl = obj.remove(CURRENT_LAYER); Document document = g.fromJson(obj, Document.class); LayerGroup root = document.getRoot(); if (root != null && currentLayerEl != null) { Layer currentLayer = null; if (currentLayerEl.isJsonArray()) { JsonArray jsonIds = currentLayerEl.getAsJsonArray(); SelectionGroup selection = new SelectionGroup(); for (JsonElement el : jsonIds) { UUID id = UUID.fromString(el.getAsString()); Layer l = root.findLayerById(id); if (l != null) { selection.addLayer(l); } } currentLayer = selection; } else { UUID currentLayerId = UUID.fromString(currentLayerEl.getAsString()); currentLayer = root.findLayerById(currentLayerId); } document.setCurrentLayer(currentLayer); } return document; } } /** * Custom serialize/deserialize class for Layer and LayerGroup objects. Calls itself recursively * to serialize all Layers in a LayerGroup tree. */ private class LayerTypeAdapter implements JsonSerializer<Layer>, JsonDeserializer<Layer> { @Override public JsonElement serialize(Layer src, Type typeOfSrc, JsonSerializationContext context) { Gson g = new Gson(); JsonObject obj = g.toJsonTree(src).getAsJsonObject(); obj.addProperty(TYPE, src.getClass().getSimpleName()); if (src instanceof LayerGroup) { JsonArray jsonLayers = new JsonArray(); LayerGroup lg = (LayerGroup) src; for (Layer l : lg.getLayers()) { jsonLayers.add(serialize(l, null, context)); } obj.add(LAYERS, jsonLayers); } return obj; } @Override public Layer deserialize(JsonElement json, Type arg1, JsonDeserializationContext arg2) throws JsonParseException { Layer layer = null; Type type = LayerGroup.class; Gson g = new Gson(); JsonObject obj = json.getAsJsonObject(); JsonElement typeEl = obj.get(TYPE); if (typeEl != null && typeEl.getAsString() != null) { String t = typeEl.getAsString(); if (t.equals(LAYER_GROUP) || t.equals(SELECTION_GROUP)) { if (t.equals(LAYER_GROUP)) { type = LayerGroup.class; } else { type = SelectionGroup.class; } JsonElement layersEl = obj.remove(LAYERS); LayerGroup layerGroup = g.fromJson(obj, type); if (layersEl != null && layersEl.getAsJsonArray() != null) { JsonArray layersArray = layersEl.getAsJsonArray(); for (JsonElement layerEl : layersArray) { Layer l = deserialize(layerEl, null, arg2); if (l != null) { layerGroup.addLayer(l); } } } layer = layerGroup; } else { if (t.equals(OVAL_LAYER)) { type = OvalLayer.class; } else if (t.equals(TRIANGLE_LAYER)) { type = TriangleLayer.class; } else { type = RectLayer.class; } ShapeLayer sl = g.fromJson(json, type); // Necessary to add change listener after deserializing because fromJson // doesn't call setColor and instead clobbers whatever color object was set in // the constructor. sl.addOnColorChangeListener(); layer = sl; } } return layer; } } }