Java tutorial
/******************************************************************************* * Copyright (c) Oct 17, 2016 Corona IDE. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * romeara - initial API and implementation and/or initial documentation *******************************************************************************/ package com.coronaide.core.service.impl; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.Reader; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Objects; import java.util.Optional; import com.coronaide.core.datastore.Datastore; import com.coronaide.core.exception.DataStorageException; import com.coronaide.core.model.Application; import com.coronaide.core.model.Module; import com.coronaide.core.model.Version; import com.coronaide.core.model.Workspace; import com.coronaide.core.service.IDatastoreService; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonWriter; /** * Implementation of {@link IDatastoreService}. Not intended for direct use by clients - use dependency injection to * obtain an instance of {@link IDatastoreService} instead * * @author romeara * @since 0.1 */ public class DatastoreService implements IDatastoreService { private static final String VERSION_FILE = "versions.json"; private final Gson gson; private final JsonParser jsonParser; /** * Creates a new data store service instance * * @since 0.1 */ public DatastoreService() { gson = new Gson(); jsonParser = new JsonParser(); } @Override public <T> void store(Application application, Module module, Datastore<T> datastore, T data) { Objects.requireNonNull(application); Objects.requireNonNull(module); Objects.requireNonNull(datastore); Objects.requireNonNull(data); try { store(application.getWorkingDirectory(), module, datastore, data); } catch (IOException e) { throw new DataStorageException( "Error storing data store for module " + module.getId() + " (" + datastore.getKey() + ")", e); } } @Override public <T> Optional<T> load(Application application, Module module, Datastore<T> datastore) { Objects.requireNonNull(application); Objects.requireNonNull(module); Objects.requireNonNull(datastore); try { return load(application.getWorkingDirectory(), module, datastore); } catch (IOException e) { throw new DataStorageException( "Error loading data store for module " + module.getId() + " (" + datastore.getKey() + ")", e); } } @Override public void clear(Application application, Module module) { Objects.requireNonNull(application); Objects.requireNonNull(module); try { clear(application.getWorkingDirectory(), module); } catch (IOException e) { throw new DataStorageException("Error clearing data store for module " + module.getId(), e); } } @Override public <T> void store(Workspace workspace, Module module, Datastore<T> datastore, T data) { Objects.requireNonNull(workspace); Objects.requireNonNull(module); Objects.requireNonNull(datastore); Objects.requireNonNull(data); try { store(workspace.getWorkingDirectory(), module, datastore, data); } catch (IOException e) { throw new DataStorageException( "Error storing data store for module " + module.getId() + " (" + datastore.getKey() + ")", e); } } @Override public <T> Optional<T> load(Workspace workspace, Module module, Datastore<T> datastore) { Objects.requireNonNull(workspace); Objects.requireNonNull(module); Objects.requireNonNull(datastore); try { return load(workspace.getWorkingDirectory(), module, datastore); } catch (IOException e) { throw new DataStorageException( "Error loading data store for module " + module.getId() + " (" + datastore.getKey() + ")", e); } } @Override public void clear(Workspace workspace, Module module) { Objects.requireNonNull(workspace); Objects.requireNonNull(module); try { clear(workspace.getWorkingDirectory(), module); } catch (IOException e) { throw new DataStorageException("Error clearing data store for module " + module.getId(), e); } } /** * Loads data for a module within a given Corona IDE working directory * * @param coronaDirectory * The Corona IDE working directory the data storage system is working within * @param module * The module data is being loaded for * @param datastore * The data store managing the data * @return The loaded data, if any - empty if no data is currently stored for the given datastore and module * @throws IOException * If there is a file system I/O error loading the data */ private <T> Optional<T> load(Path coronaDirectory, Module module, Datastore<T> datastore) throws IOException { T result = null; Path moduleDirectory = getOrCreateModuleDirectory(coronaDirectory, module); Optional<Version> existingVersion = loadVersion(moduleDirectory, datastore.getKey()); Path datastorePath = moduleDirectory.resolve(datastore.getKey()); if (existingVersion.isPresent() && Files.exists(datastorePath)) { try (BufferedReader input = Files.newBufferedReader(datastorePath)) { result = datastore.load(input); } if (!Objects.equals(existingVersion.get(), module.getVersion())) { store(coronaDirectory, module, datastore, result); } } return Optional.ofNullable(result); } /** * Stores data for the module within a given Corona IDE working directory * * @param coronaDirectory * The Corona IDE working directory the data storage system is working within * @param module * The module data is being stored for * @param datastore * The data store managing the data * @param data * The data representation to store * @throws IOException * If there is a file system I/O error storing the data */ private <T> void store(Path coronaDirectory, Module module, Datastore<T> datastore, T data) throws IOException { Path moduleDirectory = getOrCreateModuleDirectory(coronaDirectory, module); storeVersion(moduleDirectory, module.getVersion(), datastore.getKey()); Path datastoreFile = moduleDirectory.resolve(datastore.getKey()); if (!Files.exists(datastoreFile)) { Files.createFile(datastoreFile); } if (data != null) { try (BufferedWriter output = Files.newBufferedWriter(datastoreFile)) { datastore.store(data, output); } } } /** * Clears all data associated with a given module in a specific working directory * * @param coronaDirectory * The Corona IDE working directory the data storage system is working within * @param module * The module data is being cleared for * @throws IOException * If there is a file system I/O error clearing the data */ private void clear(Path coronaDirectory, Module module) throws IOException { Path moduleDirectory = getOrCreateModuleDirectory(coronaDirectory, module); Files.walkFileTree(moduleDirectory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Constructs a reference to the working directory for a module, creating it if it does not yet exist * * @param parentDir * The parent directory which should contain the module directory * @param module * The module to find the directory for * @return A reference to the module working directory * @throws IOException * If there is a file system I/O error creating the module working directory */ private Path getOrCreateModuleDirectory(Path parentDir, Module module) throws IOException { Path directory = parentDir.resolve(module.getId()); if (!Files.exists(directory)) { Files.createDirectories(directory); } return directory; } /** * Loads the current version of stored data for a given data store * * @param moduleDirectory * The working directory for files associated with the module * @param datastoreKey * The identifier of the data store the record is being loaded for * @return The current stored version of data, empty if no version is recorded * @throws IOException * If there is a file system I/O error reading the version */ private Optional<Version> loadVersion(Path moduleDirectory, String datastoreKey) throws IOException { Version result = null; Path versionPath = moduleDirectory.resolve(VERSION_FILE); if (Files.exists(versionPath)) { try (Reader reader = Files.newBufferedReader(versionPath)) { JsonElement versionElement = jsonParser.parse(reader).getAsJsonObject().get(datastoreKey); // Safe, as this is documented to return null if versionElement is null result = gson.fromJson(versionElement, Version.class); } } return Optional.ofNullable(result); } /** * Stores a version as the current version used with the given data store * * @param moduleDirectory * The working directory for files associated with the module * @param version * The current version to record for the data store * @param datastoreKey * The identifier of the data store the record is being updated for * @throws IOException * If there is a file system I/O error storing the version */ private void storeVersion(Path moduleDirectory, Version version, String datastoreKey) throws IOException { Path versionPath = moduleDirectory.resolve(VERSION_FILE); JsonObject allVersions = null; // Load the JSON if it exists, otherwise initialize the representation if (Files.exists(versionPath)) { try (Reader reader = Files.newBufferedReader(versionPath)) { allVersions = jsonParser.parse(reader).getAsJsonObject(); } } else { Files.createFile(versionPath); allVersions = new JsonObject(); } // Add/replace the entry JsonElement newVersion = gson.toJsonTree(version); allVersions.add(datastoreKey, newVersion); // Write out to file try (JsonWriter writer = gson.newJsonWriter(Files.newBufferedWriter(versionPath))) { gson.toJson(allVersions, writer); } } }