/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia; import java.awt.Container; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.StringTokenizer; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNWCUtil; import net.java.dev.spellcast.utilities.ActionPanel; import net.java.dev.spellcast.utilities.DataUtilities; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.svn.SVNManager; import net.sourceforge.kolmafia.swingui.DescriptionFrame; import net.sourceforge.kolmafia.swingui.panel.GenericPanel; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.PauseObject; import net.sourceforge.kolmafia.utilities.StringUtilities; import net.sourceforge.kolmafia.webui.RelayAgent; import net.sourceforge.kolmafia.webui.RelayServer; public abstract class StaticEntity { private static int usesSystemTray = 0; private static int usesRelayWindows = 0; private static boolean isGUIRequired = false; private static boolean isHeadless = System.getProperty( "java.awt.headless", "" ).equals( "true" ); public static final ArrayList<ActionPanel> existingPanels = new ArrayList<ActionPanel>(); private static ActionPanel[] panelArray = new GenericPanel[ 0 ]; public static String backtraceTrigger = null; private static Integer cachedSVNRevisionNumber = null; public static boolean userAborted = false; private static MafiaState globalContinuationState = MafiaState.CONTINUE; private static ThreadLocal<MafiaState> threadLocalContinuationState = new ThreadLocal<MafiaState>() { protected MafiaState initialValue() { return MafiaState.CONTINUE; } }; public static final String getVersion() { return StaticEntity.getVersion( false ); } public static final String getVersion( final boolean forceRevision ) { String version = KoLConstants.VERSION_NAME; if ( !KoLConstants.RELEASED || forceRevision ) { int revision = StaticEntity.getRevision(); if ( revision != 0 ) { version += " r" + revision; } } return version; } public static final int getRevision() { try { if ( StaticEntity.cachedSVNRevisionNumber != null ) { return StaticEntity.cachedSVNRevisionNumber; } if ( KoLConstants.REVISION == null && SVNWCUtil.isWorkingCopyRoot( KoLConstants.ROOT_LOCATION ) ) { SVNInfo info = SVNManager.doInfo( KoLConstants.ROOT_LOCATION ); StaticEntity.cachedSVNRevisionNumber = (int) info.getRevision().getNumber(); return StaticEntity.cachedSVNRevisionNumber; } } catch ( SVNException e ) { // fall through } if ( KoLConstants.REVISION == null ) { return 0; } int colonIndex = KoLConstants.REVISION.indexOf( ":" ); String revision = KoLConstants.REVISION; if ( colonIndex != -1 ) { revision = KoLConstants.REVISION.substring( 0, colonIndex ); } else if ( KoLConstants.REVISION.endsWith( "M" ) ) { revision = KoLConstants.REVISION.substring( 0, KoLConstants.REVISION.length() - 1 ); } return StringUtilities.isNumeric( revision ) ? StringUtilities.parseInt( revision ) : 0; } public static final int parseRevision( String version ) { if ( version == null ) { return 0; } if ( version.startsWith( "KoLmafia r" ) ) { version = version.substring( 10 ); } return StringUtilities.isNumeric( version ) ? StringUtilities.parseInt( version ) : 0; } public static final void setGUIRequired( boolean isGUIRequired ) { StaticEntity.isGUIRequired = isGUIRequired; } public static final boolean isGUIRequired() { return StaticEntity.isGUIRequired && !StaticEntity.isHeadless; } public static final void registerPanel( final ActionPanel panel ) { synchronized ( StaticEntity.existingPanels ) { StaticEntity.existingPanels.add( panel ); StaticEntity.getExistingPanels(); } } public static final void unregisterPanel( final ActionPanel panel ) { synchronized ( StaticEntity.existingPanels ) { StaticEntity.existingPanels.remove( panel ); StaticEntity.getExistingPanels(); } } public static final void unregisterPanels( final Container container ) { boolean removedPanel = false; synchronized ( StaticEntity.existingPanels ) { Iterator panelIterator = StaticEntity.existingPanels.iterator(); while ( panelIterator.hasNext() ) { ActionPanel panel = (ActionPanel) panelIterator.next(); if ( container.isAncestorOf( panel ) ) { panel.dispose(); panelIterator.remove(); removedPanel = true; } } } if ( removedPanel ) { StaticEntity.getExistingPanels(); } } public static final ActionPanel[] getExistingPanels() { synchronized ( StaticEntity.existingPanels ) { boolean needsRefresh = StaticEntity.panelArray.length != StaticEntity.existingPanels.size(); if ( !needsRefresh ) { for ( int i = 0; i < StaticEntity.panelArray.length && !needsRefresh; ++i ) { needsRefresh = StaticEntity.panelArray[ i ] != StaticEntity.existingPanels.get( i ); } } if ( needsRefresh ) { StaticEntity.panelArray = new ActionPanel[ StaticEntity.existingPanels.size() ]; StaticEntity.existingPanels.toArray( StaticEntity.panelArray ); } return StaticEntity.panelArray; } } public static final boolean isHeadless() { return StaticEntity.isHeadless; } public static final boolean usesSystemTray() { if ( StaticEntity.usesSystemTray == 0 ) { StaticEntity.usesSystemTray = 2; boolean useTrayIcon = Preferences.getBoolean( "useSystemTrayIcon" ); if ( !System.getProperty( "os.name" ).startsWith( "Windows" ) ) { useTrayIcon = false; } String javaArchitecture = System.getProperty( "sun.arch.data.model" ); if ( javaArchitecture == null || !javaArchitecture.equals( "32" ) ) { useTrayIcon = false; } if ( useTrayIcon ) { try { FileUtilities.loadLibrary( KoLConstants.IMAGE_LOCATION, "", "TrayIcon12.dll" ); StaticEntity.usesSystemTray = 1; } catch ( Exception e ) { } } } return StaticEntity.usesSystemTray == 1; } public static final boolean usesRelayWindows() { if ( StaticEntity.usesRelayWindows == 0 ) { StaticEntity.usesRelayWindows = Preferences.getBoolean( "useRelayWindows" ) ? 1 : 2; } return StaticEntity.usesRelayWindows == 1; } /** * A method used to open a new <code>DescriptionFrame</code> which * displays the given location, relative to the KoL home directory for * the current session. */ public static final void openDescriptionFrame( final String location ) { DescriptionFrame.showRequest( RequestEditorKit.extractRequest( location ) ); } public static final boolean executeCountdown( final String message, final int seconds ) { PauseObject pauser = new PauseObject(); StringBuffer actualMessage = new StringBuffer( message ); for ( int i = seconds; i > 0 && KoLmafia.permitsContinue(); --i ) { boolean shouldDisplay = false; // If it's the first count, then it should definitely be shown // for the countdown. if ( i == seconds ) { shouldDisplay = true; } else if ( i >= 1800 ) { shouldDisplay = i % 600 == 0; } else if ( i >= 600 ) { shouldDisplay = i % 300 == 0; } else if ( i >= 300 ) { shouldDisplay = i % 120 == 0; } else if ( i >= 60 ) { shouldDisplay = i % 60 == 0; } else if ( i >= 15 ) { shouldDisplay = i % 15 == 0; } else if ( i >= 5 ) { shouldDisplay = i % 5 == 0; } else { shouldDisplay = true; } // Only display the message if it should be displayed based on // the above checks. if ( shouldDisplay ) { actualMessage.setLength( message.length() ); if ( i >= 60 ) { int minutes = i / 60; actualMessage.append( minutes ); actualMessage.append( minutes == 1 ? " minute" : " minutes" ); if ( i % 60 != 0 ) { actualMessage.append( ", " ); } } if ( i % 60 != 0 ) { actualMessage.append( i % 60 ); actualMessage.append( i % 60 == 1 ? " second" : " seconds" ); } actualMessage.append( "..." ); KoLmafia.updateDisplay( actualMessage.toString() ); } pauser.pause( 1000 ); } return KoLmafia.permitsContinue(); } public static final void printStackTrace() { StaticEntity.printStackTrace( "Forced stack trace" ); } public static final void printStackTrace( final String message ) { StaticEntity.printStackTrace( new Exception( message ), message ); } public static final void printStackTrace( final Throwable t ) { StaticEntity.printStackTrace( t, "" ); } public static final void printStackTrace( final Throwable t, final String message ) { StaticEntity.printStackTrace( t, message, false ); } public static final void printStackTrace( final Throwable t, final String message, final boolean printOnlyCause ) { // Next, print all the information to the debug log so that // it can be sent. boolean shouldOpenStream = !RequestLogger.isDebugging(); if ( shouldOpenStream ) { RequestLogger.openDebugLog(); } String printMsg; if ( message.startsWith( "Backtrace" ) ) { StaticEntity.backtraceTrigger = null; printMsg = "Backtrace triggered, debug log printed."; } else if ( !message.equals( "" ) ) { printMsg = message; } else { printMsg = "Unexpected error, debug log printed."; } KoLmafia.updateDisplay( printMsg ); RequestLogger.updateSessionLog( printMsg ); Throwable cause = t.getCause(); if ( cause == null || !printOnlyCause ) { StaticEntity.printStackTrace( t, message, RequestLogger.getDebugStream() ); } if ( cause != null ) { StaticEntity.printStackTrace( cause, message, RequestLogger.getDebugStream() ); } if ( shouldOpenStream ) { RequestLogger.closeDebugLog(); } } private static final void printStackTrace( final Throwable t, final String message, final PrintStream ostream ) { ostream.println( t.getClass() + ": " + t.getMessage() ); t.printStackTrace( ostream ); ostream.println( message ); } private static File getJDKWorkingDirectory() { File currentJavaHome = new File( System.getProperty( "java.home" ) ); if ( StaticEntity.hasJDKBinaries( currentJavaHome ) ) { return currentJavaHome; } File javaInstallFolder = currentJavaHome.getParentFile(); if ( StaticEntity.hasJDKBinaries( javaInstallFolder ) ) { return javaInstallFolder; } File[] possibleJavaHomes = javaInstallFolder.listFiles(); for ( int i = 0; i < possibleJavaHomes.length; ++i ) { if ( StaticEntity.hasJDKBinaries( possibleJavaHomes[ i ] ) ) { return possibleJavaHomes[ i ]; } } return null; } private static boolean hasJDKBinaries( File javaHome ) { if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { return new File( javaHome, "bin/javac.exe" ).exists(); } else { return new File( javaHome, "bin/javac" ).exists(); } } public static final String getProcessId() { File javaHome = StaticEntity.getJDKWorkingDirectory(); if ( javaHome == null ) { KoLmafia.updateDisplay( "To use this feature, you must run KoLmafia with a JDK instead of a JRE." ); return null; } Runtime runtime = Runtime.getRuntime(); String pid = null; try { String[] command = new String[ 2 ]; if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { command[ 0 ] = new File( javaHome, "bin/jps.exe" ).getPath(); } else { command[ 0 ] = new File( javaHome, "bin/jps" ).getPath(); } command[ 1 ] = "-l"; Process process = runtime.exec( command ); BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) ); String line; StringBuffer sb = new StringBuffer(); while ( ( pid == null ) && ( line = reader.readLine() ) != null ) { sb.append( line ); sb.append( KoLConstants.LINE_BREAK ); if ( line.indexOf( "KoLmafia" ) != -1 ) { pid = line.substring( 0, line.indexOf( ' ' ) ); } boolean shouldOpenStream = !RequestLogger.isDebugging(); if ( shouldOpenStream ) { RequestLogger.openDebugLog(); } RequestLogger.getDebugStream().println( sb.toString() ); if ( shouldOpenStream ) { RequestLogger.closeDebugLog(); } } } catch ( IOException e ) { e.printStackTrace(); } if ( pid != null ) { return pid; } KoLmafia.updateDisplay( "Unable to determine KoLmafia process id." ); return null; } public static final void printThreadDump() { File javaHome = StaticEntity.getJDKWorkingDirectory(); if ( javaHome == null ) { KoLmafia.updateDisplay( "To use this feature, you must run KoLmafia with a JDK instead of a JRE." ); return; } String pid = StaticEntity.getProcessId(); if ( pid == null ) { return; } KoLmafia.updateDisplay( "Generating thread dump for KoLmafia process id " + pid + "..." ); Runtime runtime = Runtime.getRuntime(); StringBuffer sb = new StringBuffer(); try { String[] command = new String[ 2 ]; if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { command[ 0 ] = new File( javaHome, "bin/jstack.exe" ).getPath(); } else { command[ 0 ] = new File( javaHome, "bin/jstack" ).getPath(); } command[ 1 ] = pid; Process process = runtime.exec( command ); BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) ); String line; while ( ( line = reader.readLine() ) != null ) { sb.append( line ); sb.append( KoLConstants.LINE_BREAK ); } reader.close(); } catch ( IOException e ) { e.printStackTrace(); } boolean shouldOpenStream = !RequestLogger.isDebugging(); if ( shouldOpenStream ) { RequestLogger.openDebugLog(); } RequestLogger.getDebugStream().println( sb.toString() ); if ( shouldOpenStream ) { RequestLogger.closeDebugLog(); } } public static final void generateHeapDump() { File javaHome = StaticEntity.getJDKWorkingDirectory(); if ( javaHome == null ) { KoLmafia.updateDisplay( "To use this feature, you must run KoLmafia with a JDK instead of a JRE." ); return; } String pid = StaticEntity.getProcessId(); if ( pid == null ) { return; } KoLmafia.updateDisplay( "Generating heap dump for KoLmafia process id " + pid + "..." ); Runtime runtime = Runtime.getRuntime(); StringBuffer sb = new StringBuffer(); try { String[] command = new String[ 3 ]; if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { command[ 0 ] = new File( javaHome, "bin/jmap.exe" ).getPath(); } else { command[ 0 ] = new File( javaHome, "bin/jmap" ).getPath(); } String javaVersion = System.getProperty( "java.runtime.version" ); if ( javaVersion.contains( "1.5.0_" ) ) { command[ 1 ] = "-heap:format=b"; } else { int fileIndex = 0; String jmapFileName = null; File jmapFile = null; do { ++fileIndex; jmapFileName = "kolmafia" + fileIndex + ".hprof"; jmapFile = new File( KoLConstants.ROOT_LOCATION, jmapFileName ); } while ( jmapFile.exists() ); command[ 1 ] = "-dump:format=b,file=" + jmapFileName; } command[ 2 ] = pid; Process process = runtime.exec( command, new String[ 0 ], KoLConstants.ROOT_LOCATION ); BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) ); String line; while ( ( line = reader.readLine() ) != null ) { sb.append( line ); sb.append( KoLConstants.LINE_BREAK ); } reader.close(); } catch ( IOException e ) { e.printStackTrace(); } boolean shouldOpenStream = !RequestLogger.isDebugging(); if ( shouldOpenStream ) { RequestLogger.openDebugLog(); } RequestLogger.getDebugStream().println( sb.toString() ); if ( shouldOpenStream ) { RequestLogger.closeDebugLog(); } } public static final String[] getPastUserList() { ArrayList<String> pastUserList = new ArrayList<String>(); String user; File[] files = DataUtilities.listFiles( KoLConstants.SETTINGS_LOCATION ); for ( int i = 0; i < files.length; ++i ) { user = files[ i ].getName(); if ( user.startsWith( "GLOBAL" ) || !user.endsWith( "_prefs.txt" ) ) { continue; } user = user.substring( 0, user.length() - 10 ); if ( !user.equals( "GLOBAL" ) && !pastUserList.contains( user ) ) { pastUserList.add( user ); } } String[] pastUsers = new String[ pastUserList.size() ]; pastUserList.toArray( pastUsers ); return pastUsers; } public static final void disable( final String name ) { String functionName; StringTokenizer tokens = new StringTokenizer( name, ", " ); while ( tokens.hasMoreTokens() ) { functionName = tokens.nextToken(); if ( !KoLConstants.disabledScripts.contains( functionName ) ) { KoLConstants.disabledScripts.add( functionName ); } } } public static final void enable( final String name ) { if ( name.equals( "all" ) ) { KoLConstants.disabledScripts.clear(); return; } StringTokenizer tokens = new StringTokenizer( name, ", " ); while ( tokens.hasMoreTokens() ) { KoLConstants.disabledScripts.remove( tokens.nextToken() ); } } public static final boolean isDisabled( final String name ) { if ( name.equals( "enable" ) || name.equals( "disable" ) ) { return false; } return KoLConstants.disabledScripts.contains( "all" ) || KoLConstants.disabledScripts.contains( name ); } public static final MafiaState getContinuationState() { if ( isRelayThread() ) return StaticEntity.threadLocalContinuationState.get(); return StaticEntity.globalContinuationState; } public static void setContinuationState( MafiaState state ) { if ( isRelayThread() ) StaticEntity.threadLocalContinuationState.set( state ); else StaticEntity.globalContinuationState = state; } static final boolean isRelayThread() { return RelayServer.agentThreads.contains( Thread.currentThread() ) || Thread.currentThread() == RelayAgent.COMBAT_THREAD; } }