Skip to content

Enhance SaveGame: java.nio.file, Try-with-Resources & Improved Error Handling #2494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 53 additions & 62 deletions jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2021 jMonkeyEngine
* Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -36,25 +36,33 @@
import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.system.JmeSystem;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
* Tool for saving Savables as SaveGame entries in a system-dependent way.
*
* @author normenhansen
*/
public class SaveGame {

/**
* The logger instance for this class. Used to log messages, warnings, and errors.
*/
private static final Logger logger = Logger.getLogger(SaveGame.class.getName());

/**
* A private constructor to inhibit instantiation of this class.
*/
Expand All @@ -80,47 +88,33 @@ public static void saveGame(String gamePath, String dataName, Savable data) {
*/
public static void saveGame(String gamePath, String dataName, Savable data, JmeSystem.StorageFolderType storageType) {
if (storageType == null) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Base Storage Folder Type is null, using External!");
logger.log(Level.WARNING, "Storage folder type is null. Using {0} as default.", JmeSystem.StorageFolderType.External);
storageType = JmeSystem.StorageFolderType.External;
}

BinaryExporter ex = BinaryExporter.getInstance();
OutputStream os = null;
try {
File baseFolder = JmeSystem.getStorageFolder(storageType);
if (baseFolder == null) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
throw new IllegalStateException("SaveGame dataset cannot be created");
throw new IllegalStateException("Failed to get base storage folder for type: " + storageType);
}
File daveFolder = new File(baseFolder.getAbsolutePath() + File.separator + gamePath.replace('/', File.separatorChar));
if (!daveFolder.exists() && !daveFolder.mkdirs()) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
throw new IllegalStateException("SaveGame dataset cannot be created");
}
File saveFile = new File(daveFolder.getAbsolutePath() + File.separator + dataName);
if (!saveFile.exists()) {
if (!saveFile.createNewFile()) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
throw new IllegalStateException("SaveGame dataset cannot be created");
}
}
os = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(saveFile)));
ex.save(data, os);
Logger.getLogger(SaveGame.class.getName()).log(Level.FINE, "Saving data to: {0}", saveFile.getAbsolutePath());
} catch (IOException ex1) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
ex1.printStackTrace();
throw new IllegalStateException("SaveGame dataset cannot be saved");
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException ex1) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
ex1.printStackTrace();
throw new IllegalStateException("SaveGame dataset cannot be saved");

Path saveFolderPath = Paths.get(baseFolder.getAbsolutePath(), gamePath.replace('/', File.separatorChar));
Path saveFilePath = saveFolderPath.resolve(dataName);

// Ensure directories exist
Files.createDirectories(saveFolderPath);

try (OutputStream os = Files.newOutputStream(saveFilePath);
GZIPOutputStream gzip = new GZIPOutputStream(new BufferedOutputStream(os))) {

BinaryExporter exporter = BinaryExporter.getInstance();
exporter.save(data, gzip);
logger.log(Level.INFO, "Successfully saved data to: {0}", saveFilePath.toAbsolutePath());
}
} catch (IOException | IllegalStateException ex) {
logger.log(Level.SEVERE, "Error saving data for gamePath: {0}, dataName: {1}. Exception: {2}",
new Object[]{gamePath, dataName, ex.getMessage()});
throw new IllegalStateException("SaveGame dataset cannot be saved.", ex);
}
}

Expand Down Expand Up @@ -160,48 +154,45 @@ public static Savable loadGame(String gamePath, String dataName, AssetManager ma
* Loads a savable that has been saved on this system with saveGame() before.
* @param gamePath A unique path for this game, e.g. com/mycompany/mygame
* @param dataName A unique name for this SaveGame, e.g. "save_001"
* @param manager Link to an AssetManager if required for loading the data (e.g. models with textures)
* @param assetManager Link to an AssetManager if required for loading the data (e.g. models with textures)
* @param storageType The specific type of folder to use to save the data
* @return The savable that was saved or null if none was found
*/
public static Savable loadGame(String gamePath, String dataName, AssetManager manager, JmeSystem.StorageFolderType storageType) {
public static Savable loadGame(String gamePath, String dataName, AssetManager assetManager, JmeSystem.StorageFolderType storageType) {
if (storageType == null) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Base Storage Folder Type is null, using External!");
logger.log(Level.WARNING, "Storage folder type is null. Using {0} as default.", JmeSystem.StorageFolderType.External);
storageType = JmeSystem.StorageFolderType.External;
}

InputStream is = null;
Savable sav = null;
try {
File baseFolder = JmeSystem.getStorageFolder(storageType);
if (baseFolder == null) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error reading base storage folder!");
logger.log(Level.SEVERE, "Error reading base storage folder for type: {0}", storageType);
return null;
}
File file = new File(baseFolder.getAbsolutePath() + File.separator + gamePath.replace('/', File.separatorChar) + File.separator + dataName);
if(!file.exists()){
Path loadFilePath = Paths.get(baseFolder.getAbsolutePath(), gamePath.replace('/', File.separatorChar), dataName);

// Check if the file exists before attempting to load
if (!Files.exists(loadFilePath)) {
logger.log(Level.INFO, "Save game file not found: {0}", loadFilePath.toAbsolutePath());
return null;
}
is = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file)));
BinaryImporter imp = BinaryImporter.getInstance();
if (manager != null) {
imp.setAssetManager(manager);
}
sav = imp.load(is);
Logger.getLogger(SaveGame.class.getName()).log(Level.FINE, "Loading data from: {0}", file.getAbsolutePath());
} catch (IOException ex) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
ex.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ex) {
Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
ex.printStackTrace();

try (InputStream is = Files.newInputStream(loadFilePath);
GZIPInputStream gzip = new GZIPInputStream(new BufferedInputStream(is))) {

BinaryImporter importer = BinaryImporter.getInstance();
if (assetManager != null) {
importer.setAssetManager(assetManager);
}
Savable savable = importer.load(gzip);
logger.log(Level.INFO, "Successfully loaded data from: {0}", loadFilePath.toAbsolutePath());
return savable;
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error loading data from gamePath: {0}, dataName: {1}. Exception: {2}",
new Object[]{gamePath, dataName, ex.getMessage()});
return null;
}
return sav;
}
}