Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
mrzhenya committed Apr 4, 2024
2 parents 75725ac + 9293b5d commit a3172de
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 77 deletions.
10 changes: 3 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>net.curre.jjeopardy</groupId>
<artifactId>jjeopardy</artifactId>
<packaging>jar</packaging>
<version>0.2.0</version>
<version>0.3.0</version>
<properties>
<!-- User facing application name. -->
<project.appName>JJeopardy</project.appName>
Expand All @@ -19,8 +19,8 @@
<!-- Application copyright info, included in the distribution packages. -->
<project.appCopyright>Copyright © 2024 Yevgeny Nyden</project.appCopyright>

<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.9</maven.compiler.source>
<maven.compiler.target>1.9</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
Expand Down Expand Up @@ -94,10 +94,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/net/curre/jjeopardy/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@
import net.curre.jjeopardy.service.Registry;
import net.curre.jjeopardy.service.SettingsService;
import net.curre.jjeopardy.ui.landing.LandingUi;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.layout.PatternLayout;

import javax.swing.SwingUtilities;
import java.io.File;

/**
* The driver to run the JJeopardy application.
Expand All @@ -36,11 +43,17 @@ public class App {
/** Private class logger. */
private static final Logger logger = LogManager.getLogger(App.class.getName());

/** Name of the log file (will be created in the settings directory). */
private static final String LOG_FILENAME = "jjeopardy.log";

/**
* Main method to run the JJeopardy application.
* @param args Argument array.
*/
public static void main(String[] args) {

// Add file appender to log4j configuration.
updateLogConfiguration();
logger.info("Starting application...");

// Initialize the main service registry with a default (non-test) one.
Expand All @@ -62,4 +75,35 @@ public static void main(String[] args) {
registry.setLandingUi(landingUi);
});
}

/**
* Adds a file appender to log4j configuration. It has to be done programmatically
* (vs via log4j2.xml) because the log filepath needs to be determined at run time.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static void updateLogConfiguration() {
final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
final Configuration config = loggerContext.getConfiguration();
final Layout layout = PatternLayout.newBuilder()
.withPattern("%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n")
.withConfiguration(config).build();
String logFilePath = SettingsService.getVerifiedSettingsDirectoryPath() + File.separatorChar + LOG_FILENAME;
System.out.println("logFilePath = " + logFilePath);
FileAppender.Builder builder = FileAppender.newBuilder();
builder.withFileName(logFilePath)
.withAppend(false)
.withLocking(false)
.setIgnoreExceptions(true);
builder.setName("JJeopardyLogFile");
builder.setBufferedIo(true);
builder.setBufferSize(4000);
builder.setLayout(layout);
builder.setImmediateFlush(true);
builder.setConfiguration(config);
FileAppender appender = builder.build();
appender.start();

config.getRootLogger().addAppender(appender, Level.INFO, null);
loggerContext.updateLoggers();
}
}
130 changes: 118 additions & 12 deletions src/main/java/net/curre/jjeopardy/games/DefaultGames.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,146 @@

package net.curre.jjeopardy.games;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
* Serves as an anchor to the default games resource folder and a utility
* to load the default games.
* to copy default games (packaged with the game) to the games library folder.
*
* @author Yevgeny Nyden
*/
public class DefaultGames {

/** Private class logger. */
private static final Logger logger = LogManager.getLogger(DefaultGames.class.getName());

/**
* Gets the default game bundle from the default games folder.
* @return a list of File objects representing default game bundles
* @throws URISyntaxException on loading error
* Copies prepackaged games from the resource directory (or a jar) to the library
* games folder. Only game bundles (not single game files) are supported.
* @param libGamesDir an absolute path to the game library folder (under game settings)
* @throws URISyntaxException on URI syntax errors
* @throws IOException on input/output errors
*/
public static List<File> getDefaultGameBundles() throws URISyntaxException {
final List<File> bundles = new ArrayList<>();
public static void copyDefaultLibraryGames(Path libGamesDir) throws URISyntaxException, IOException {
// Obtaining the list of game bundles (directories) in the games directory.
URL resource = DefaultGames.class.getResource("");
for (File gameBundle : Objects.requireNonNull(Paths.get(resource.toURI()).toFile().listFiles())) {
if (StringUtils.startsWith(gameBundle.getName(), DefaultGames.class.getSimpleName())) {
// Skipping the class file.
URL gameDirResource = DefaultGames.class.getResource("");

if (gameDirResource == null) {
logger.error("Unable to find prepackaged library games resource");
} else if (gameDirResource.getPath().startsWith("file:")) {
// Running in prod mode (reading from jar).
String rootPath = StringUtils.split(gameDirResource.getPath(), "!")[1].substring(1);
String jarPath = StringUtils.substringBetween(gameDirResource.getPath(), "file:", "!");
copyFromJarFile(jarPath, rootPath, libGamesDir);
} else {
// Running in dev mode (reading from files).
File sourceDir = Paths.get(gameDirResource.toURI()).toFile();
copyFromFileSystem(sourceDir, libGamesDir);
}
}

/**
* Copies game bundles from the jar to the specified library location.
* @param jarPath absolute path to the game jar file
* @param gamesResourcePath resource path to the games data
* @param libGamesDir absolute path to the games library folder
* @throws IOException on input/output errors
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private static void copyFromJarFile(String jarPath, String gamesResourcePath, Path libGamesDir) throws IOException {
logger.info("Opening the app jar: " + jarPath);
FileInputStream fileInputStream = new FileInputStream(jarPath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);

// Go over the zip file entries and copy the ones that match our pattern.
int rootPathCharPivot = gamesResourcePath.length();
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.getName().startsWith(gamesResourcePath)) {
logger.debug(":::: resource " + zipEntry.getName().substring(rootPathCharPivot));
String gameResource = zipEntry.getName().substring(rootPathCharPivot);
String[] parts = gameResource.split("/");
// Only pick paths that have both 'bundle' and 'file' (game data file).
if (parts.length == 2) {
String bundleName = parts[0];
String fileName = parts[1];
File destDir = new File(libGamesDir.toString() + File.separatorChar + bundleName);
if (!destDir.exists()) {
destDir.mkdir();
}
String destFileName = destDir.toString() + File.separatorChar + fileName;
extractZipEntry(zipInputStream, destFileName);
} else if (parts.length > 2) {
// TODO: add support for images ('bundle' / 'games' / 'file').
logger.warn("Library games with directories are not supported yet: " + gameResource);
}
}
}
zipInputStream.close();
}

/**
* Extracts a single zip file entry/file - copies it to the destination filepath.
* @param zipInputStream zip input stream to read
* @param destFilePath absolute path to the filename where zip entry is copied to
* @throws IOException on input/output errors
*/
private static void extractZipEntry(ZipInputStream zipInputStream, String destFilePath) throws IOException {
OutputStream outStream = Files.newOutputStream(Paths.get(destFilePath));
byte[] buffer = new byte[9000];
int length;
while ((length = zipInputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, length);
}
outStream.close();
}

/**
* Copies prepackaged games from the filesystem to the games library folder.
* @param sourceDir source directory where library game bundles are located
* @param libGamesDir an absolute path to the game library folder (under game settings)
* where games are copied to
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
private static void copyFromFileSystem(File sourceDir, Path libGamesDir) throws IOException {
// List the game bundles in the resources games directory.
final List<File> gameBundles = new ArrayList<>();
for (File gameBundle : Objects.requireNonNull(sourceDir.listFiles())) {
if (gameBundle.isFile()) {
// Skipping any non-directory item.
continue;
}
bundles.add(gameBundle);
gameBundles.add(gameBundle);
}

// Copy resource game directories and their content to the library games directory.
for (File originalBundle : gameBundles) {
File destDir = new File(libGamesDir.toString() + File.separatorChar + originalBundle.getName());
destDir.mkdir();
for (File file : Objects.requireNonNull(originalBundle.listFiles())) {
File destFile = new File(destDir.toString() + File.separatorChar + file.getName());
FileUtils.copyFile(file, destFile);
}
}
return bundles;
}
}
15 changes: 5 additions & 10 deletions src/main/java/net/curre/jjeopardy/service/GameDataService.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,23 +206,18 @@ public boolean isGameReady() {
public static void copyDefaultGamesToLibraryIfNeeded() {
Path gamesDir = Paths.get(getGameLibraryDirectoryPath());
if (Files.exists(gamesDir)) {
logger.info("It appears that the library games were copied already to: " + gamesDir);
// If the games directory exists, assume the files have been copied there already.
return;
}
logger.info("Copying the library games to: " + gamesDir);
try {
gamesDir.toFile().mkdir();
List<File> gameBundles = DefaultGames.getDefaultGameBundles();
for (File originalBundle : gameBundles) {
File destDir = new File(gamesDir.toString() + File.separatorChar + originalBundle.getName());
destDir.mkdir();
for (File file : Objects.requireNonNull(originalBundle.listFiles())) {
File destFile = new File(destDir.toString() + File.separatorChar + file.getName());
FileUtils.copyFile(file, destFile);
}
}
DefaultGames.copyDefaultLibraryGames(gamesDir);
} catch (Exception e) {
logger.log(Level.WARN, "Unable to copy default game files", e);
logger.log(Level.ERROR, "Unable to copy default game files", e);
}
logger.info("Finished copying the library games");
}

/**
Expand Down
37 changes: 26 additions & 11 deletions src/main/java/net/curre/jjeopardy/service/LocaleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package net.curre.jjeopardy.service;

import net.curre.jjeopardy.App;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
Expand All @@ -41,6 +40,9 @@ public class LocaleService {
/** Private class logger. */
private static final Logger logger = LogManager.getLogger(LocaleService.class.getName());

/** Currently selected locale for the JJeopardy app. */
private static Locale currentLocale = DEFAULT_LOCALE;

/** List of available locales in the application. */
private final List<Locale> availableLocales;

Expand All @@ -55,7 +57,6 @@ protected LocaleService() {
this.availableLocales.add(new Locale(localeParts[0], localeParts[1]));
}
}
Locale.setDefault(DEFAULT_LOCALE);
}

/**
Expand All @@ -66,6 +67,14 @@ public List<Locale> getAvailableLocales() {
return this.availableLocales;
}

/**
* Gets the currently selected locale for the JJeopardy app.
* @return locale to use in the app
*/
public static Locale getCurrentLocale() {
return currentLocale;
}

/**
* Gets the string resource for the given key. If any keyArgs
* are passed, they will replace the "{0}", "{1}", etc. string
Expand All @@ -77,10 +86,9 @@ public List<Locale> getAvailableLocales() {
* @return string resource for the given key
*/
public static String getString(String key, String... keyArgs) {
Locale locale = Locale.getDefault();
String bundleName = "messages";
if (!locale.equals(DEFAULT_LOCALE)) {
bundleName += "_" + locale;
if (!currentLocale.equals(DEFAULT_LOCALE)) {
bundleName += "_" + currentLocale;
}
String message = ResourceBundle.getBundle(bundleName).getString(key);
if (keyArgs != null) {
Expand All @@ -99,15 +107,14 @@ public static String getString(String key, String... keyArgs) {
}

/**
* Setter for the current locale.
* @param localeId Identifier for the locale (Locale.toString())
* @param showDialog true if show restart info dialog on change
* Setter for the current locale used by the application.
* @param localeId identifier for the locale (Locale.toString())
* @param showDialog true if locale changed; false if otherwise
*/
public synchronized void setCurrentLocale(String localeId, boolean showDialog) {
try {
Locale prevLocale = Locale.getDefault();
Locale locale = findLocaleById(localeId);
Locale.setDefault(locale);
Locale prevLocale = LocaleService.getCurrentLocale();
currentLocale = findLocaleById(localeId);

if (!localeId.equals(prevLocale.toString()) && showDialog) {
AppRegistry.getInstance().getUiService().showRestartGameDialog();
Expand All @@ -134,4 +141,12 @@ public Locale findLocaleById(String localeId) {
logger.warn("Locale \"" + localeId + "\" is not found!");
return DEFAULT_LOCALE;
}

/**
* Sets the current locale used by the application.
* @param locale locale to set as the current locale
*/
protected static void setCurrentLocale(Locale locale) {
currentLocale = locale;
}
}
Loading

0 comments on commit a3172de

Please sign in to comment.