freecol/src/net/sf/freecol/common/debug/FreeColDebugger.java

506 lines
15 KiB
Java

/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.common.debug;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardOpenOption.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.common.io.FreeColDirectories;
import net.sf.freecol.common.model.Player;
import static net.sf.freecol.common.util.CollectionUtils.*;
import net.sf.freecol.common.util.LogBuilder;
import static net.sf.freecol.common.util.StringUtils.*;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.ai.military.DefensiveMap;
/**
* High-level debug handling.
*/
public class FreeColDebugger {
private static final Logger logger = Logger.getLogger(FreeColDebugger.class.getName());
/** The debug modes, any of which may be active. */
public static enum DebugMode {
COMMS, // Trace print full c-s communications, and verbose
// (non-i18n) server errors.
DESYNC,// Check frequently for desynchronization
MENUS, // Enable the Debug menu, the extra commands in
// ColonyPanel and TilePopup, the goods-in-market
// tooltip in MarketLabel, the extra modifiers on the
// BuildingToolTip, the region and Mission
// displays in MapViewer, taking over AI players,
// and turn skipping.
INIT, // An initial colony is made, and goods added to all
// native settlements.
PATHS // Display more information on goto paths
}
private static int debugMode = 0;
/**
* The number of turns to run without stopping.
*/
private static int debugRunTurns = -1;
/**
* The name of a file to save to at the end of a debug run.
*/
private static String debugRunSave = null;
/**
* Stores the standard fog of war setting when revealing all the map
* Allows restore to previous state when re-enabling normal vision
*/
private static boolean normalGameFogOfWar = false;
/**
* Displays elements for debugging the rendering.
*/
private static boolean debugRendering = false;
/** Display map coordinates? */
private static boolean displayCoordinates = false;
/** Display tile values as a colony site for this player? */
private static Player displayColonyValuePlayer = null;
/** Show the mission for an AI unit? */
private static boolean showMission = false;
/** Show full mission information? */
private static boolean showMissionInfo = false;
/** Stream for debugLog. */
private static final AtomicReference<PrintStream> debugStream
= new AtomicReference<PrintStream>(null);
/** Displays the defence map for this player */
private static Player defenceMapPlayer = null;
/**
* Is a debug mode enabled in this game?
*
* @return True if any debug mode is enabled.
*/
public static boolean isInDebugMode() {
return FreeColDebugger.debugMode != 0;
}
/**
* Checks if automatic changing of the scaleFactor on window resize
* has been enabled.
*
* @return {@code true} if any debug mode is enabled.
*/
public static boolean isAutomaticRescalingOnWindowResize() {
return FreeColDebugger.debugMode != 0;
}
/**
* Is a particular debug mode enabled in this game?
*
* @param mode The {@code DebugMode} to test.
* @return True if the specified mode is enabled.
*/
public static boolean isInDebugMode(DebugMode mode) {
return ((1 << mode.ordinal()) & FreeColDebugger.debugMode) != 0;
}
/**
* Set a debug mode.
*
* @param mode The {@code DebugMode} to set.
* @param val The value to set to.
*/
public static void setDebugMode(DebugMode mode, boolean val) {
if (val) {
enableDebugMode(mode);
} else {
disableDebugMode(mode);
}
}
/**
* Enables a particular debug mode.
*
* @param mode The {@code DebugMode} to enable.
*/
public static void enableDebugMode(DebugMode mode) {
FreeColDebugger.debugMode |= 1 << mode.ordinal();
}
/**
* Disable a particular debug mode.
*
* @param mode The {@code DebugMode} to disable.
*/
private static void disableDebugMode(DebugMode mode) {
if (mode != DebugMode.MENUS) { // Can not leave menus mode!
FreeColDebugger.debugMode &= ~(1 << mode.ordinal());
}
}
/**
* Gets the debug modes.
*
* @return A string containing the modes as csv.
*/
public static String getDebugModes() {
return transform(DebugMode.values(), m -> isInDebugMode(m),
DebugMode::toString, Collectors.joining(","));
}
/**
* Configures the debug modes.
*
* @param optionValue The command line option.
* @return True if the modes were set as specified.
*/
public static boolean setDebugModes(String optionValue) {
if (optionValue == null) return false;
if (optionValue.isEmpty()) return true;
for (String s : optionValue.split(",")) {
try {
DebugMode mode = Enum.valueOf(DebugMode.class, upCase(s));
enableDebugMode(mode);
} catch (RuntimeException e) {
logger.warning("Unrecognized debug mode: " + optionValue);
return false;
}
}
return true;
}
/**
* Configures a debug run.
*
* @param option The command line option.
*/
public static void configureDebugRun(String option) {
int comma = option.indexOf(',');
String turns = option.substring(0, (comma < 0) ? option.length()
: comma);
try {
setDebugRunTurns(Integer.parseInt(turns));
} catch (NumberFormatException e) {
setDebugRunTurns(-1);
}
if (comma > 0) setDebugRunSave(option.substring(comma + 1));
}
/**
* Gets the turns to run in debug mode.
*
* @return The turns to run in debug mode.
*/
public static int getDebugRunTurns() {
return FreeColDebugger.debugRunTurns;
}
/**
* Sets the number of turns to run in debug mode.
*
* @param debugRunTurns The new number of debug turns.
*/
public static void setDebugRunTurns(int debugRunTurns) {
FreeColDebugger.debugRunTurns = debugRunTurns;
}
/**
* Gets the debug save file name.
*
* @return The debug save file name.
*/
public static String getDebugRunSave() {
return FreeColDebugger.debugRunSave;
}
/**
* Sets the debug save file name.
*
* @param debugRunSave The new debug save file name.
*/
public static void setDebugRunSave(String debugRunSave) {
FreeColDebugger.debugRunSave = debugRunSave;
}
/**
* Get the normal fog of war setting.
*
* @return The normal fog of war setting.
*/
public static boolean getNormalGameFogOfWar() {
return normalGameFogOfWar;
}
/**
* Set the normal fog of war setting.
*
* @param normalGameFogOfWar The new normal fog of war setting.
*/
public static void setNormalGameFogOfWar(boolean normalGameFogOfWar) {
FreeColDebugger.normalGameFogOfWar = normalGameFogOfWar;
}
/**
* Try to complete a debug run if one is happening.
*
* @param freeColClient The {@code FreeColClient} for the game.
* @param force Force early completion of a run.
* @return True if a debug run was completed.
*/
public static boolean finishDebugRun(FreeColClient freeColClient,
boolean force) {
int turns = getDebugRunTurns();
if (turns < 0 // Not a debug run
|| turns > 0 && !force // Still going
) return false;
// Zero => signalEndDebugRun was called
setDebugRunTurns(-1);
if (getDebugRunSave() != null) {
FreeColServer fcs = freeColClient.getFreeColServer();
if (fcs != null) {
try {
fcs.saveGame(FreeColDirectories.getDebugRunSaveFile(),
freeColClient.getClientOptions(), null);
} catch (IOException e) {}
}
freeColClient.quit();
}
return true;
}
/**
* Signal that a debug run should complete at the next suitable
* opportunity. Currently called from the server.
*/
public static void signalEndDebugRun() {
if (debugRunTurns > 0) setDebugRunTurns(0);
}
/**
* Should elements for debugging the rendering be displayed?
*/
public static boolean debugRendering() {
return debugRendering;
}
/**
* Displays elements for debugging the rendering.
*
* @param _debugRendering If true, displays elements for debugging the rendering.
*/
public static void setDebugRendering(boolean _debugRendering) {
debugRendering = _debugRendering;
}
/**
* Should the map viewer display tile coordinates?
*
* @return True if the map viewer should display coordinates.
*/
public static boolean debugDisplayCoordinates() {
return displayCoordinates;
}
/**
* Set the display tile coordinates state.
*
* @param display Whether to display or not.
*/
public static void setDebugDisplayCoordinates(boolean display) {
displayCoordinates = display;
}
/**
* Should the map viewer display colony values for a player?
*
* @return The {@code Player} to display colony values for,
* or null if not to display.
*/
public static Player debugDisplayColonyValuePlayer() {
return displayColonyValuePlayer;
}
/**
* Set the player to display colony values for.
*
* @param display The new {@code Player} to display for.
*/
public static void setDebugDisplayColonyValuePlayer(Player display) {
displayColonyValuePlayer = display;
}
/**
* Should the map viewer show AI missions?
*
* @return True if the map viewer should show AI missions?
*/
public static boolean debugShowMission() {
return showMission;
}
/**
* Set the display of AI missions state.
*
* @param display Whether to display or not.
*/
public static void setDebugShowMission(boolean display) {
showMission = display;
}
/**
* Should the map viewer show full AI mission info?
*
* @return True if the map viewer should show full AI mission info.
*/
public static boolean debugShowMissionInfo() {
return showMissionInfo;
}
/**
* Set the display of full AI mission info state.
*
* @param display Whether to display or not.
*/
public static void setDebugShowMissionInfo(boolean display) {
showMissionInfo = display;
}
/**
* Displays the {@link DefensiveMap} for a given player.
*/
public static void setDebugShowDefenceMapForPlayer(Player defenceMapPlayer) {
FreeColDebugger.defenceMapPlayer = defenceMapPlayer;
}
public static Player debugShowDefenceMapForPlayer() {
return defenceMapPlayer;
}
/**
* Handler for log records that include a crash.
*/
public static void handleCrash() {
if (debugRunSave != null) signalEndDebugRun();
}
/**
* Emergency run time log to use when the normal logging is failing.
* It might as well be here.
*
* @param msg The message to log.
*/
public static void debugLog(String msg) {
PrintStream print = debugStream.get();
if (print == null) {
String tmp = System.getenv("TMPDIR");
if (tmp == null) tmp = "/tmp";
final Path path = Paths.get(tmp, "freecol.debug");
try {
OutputStream fos
= Files.newOutputStream(path, CREATE, APPEND);
print = new PrintStream(fos, true, "UTF-8");
} catch (IOException ex) {
; // ignored
}
debugStream.set(print);
}
if (print != null) print.println(msg);
}
/**
* Miscellaneous debug helper to get a string representation of
* the current call stack.
*
* @return A stack trace as a string.
*/
public static String stackTraceToString() {
return stackTraceToString(Thread.currentThread());
}
/**
* Miscellaneous debug helper to get a string representation of
* the stack of a given thread.
*
* @param thread The {@code Thread} to print.
* @return A stack trace as a string.
*/
private static String stackTraceToString(Thread thread) {
LogBuilder lb = new LogBuilder(512);
addStackTrace(lb, thread);
return lb.toString();
}
/**
* Helper that adds a stack trace for the current thread to a log builder.
*
* @param lb The {@code LogBuilder} to add to.
*/
public static void addStackTrace(LogBuilder lb) {
addStackTrace(lb, Thread.currentThread());
}
/**
* Helper that adds a stack trace to a log builder.
*
* @param lb The {@code LogBuilder} to add to.
* @param thread The {@code Thread} to print.
*/
private static void addStackTrace(LogBuilder lb, Thread thread) {
for (StackTraceElement s : thread.getStackTrace()) {
lb.add(s.toString(), "\n");
}
lb.shrink("\n");
}
/**
* Log a warning with a stack trace.
*
* @param logger The {@code Logger} to log to.
* @param warn The warning message.
*/
public static void trace(Logger logger, String warn) {
LogBuilder lb = new LogBuilder(512);
lb.add(warn, "\n");
addStackTrace(lb);
if (logger == null) {
System.err.println(lb.toString());
} else {
logger.warning(lb.toString());
}
}
}