/** * 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.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.SwingUtilities; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.chat.ChatManager; import net.sourceforge.kolmafia.chat.InternalMessage; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.session.ResponseTextParser; import net.sourceforge.kolmafia.swingui.SystemTrayFrame; import net.sourceforge.kolmafia.utilities.PauseObject; public abstract class RequestThread { private static final AtomicInteger nextRequestId = new AtomicInteger(); private static final Map<Integer,Thread> threadMap = new HashMap<Integer,Thread>(); private static final ExecutorService EXECUTOR; static { int fixedPoolSize = Preferences.getInteger( "fixedThreadPoolSize" ); if ( fixedPoolSize > 0 ) EXECUTOR = Executors.newFixedThreadPool( fixedPoolSize ); else EXECUTOR = Executors.newCachedThreadPool(); } public static final void runInParallel( final Runnable action ) { RequestThread.runInParallel( action, true ); } public static final void runInParallel( final Runnable action, final boolean sequence ) { EXECUTOR.submit( new SequencedRunnable( action, sequence ) ); } public static final void postRequestAfterInitialization( final GenericRequest request ) { RequestThread.runInParallel( new PostDelayedRequestRunnable( request ) ); } private static class PostDelayedRequestRunnable implements Runnable { private final GenericRequest request; private final PauseObject pauser; public PostDelayedRequestRunnable( final GenericRequest request ) { this.request = request; this.pauser = new PauseObject(); } public void run() { while ( KoLmafia.isRefreshing() ) { this.pauser.pause( 100 ); } RequestThread.postRequest( this.request ); } } public static final void executeMethodAfterInitialization( final Object object, final String method ) { RequestThread.runInParallel( new ExecuteDelayedMethodRunnable( object, method ) ); } private static class ExecuteDelayedMethodRunnable implements Runnable { private Class objectClass; private Object object; private final String methodName; private Method method; private final PauseObject pauser; public ExecuteDelayedMethodRunnable( final Object object, final String methodName ) { if ( object instanceof Class ) { this.objectClass = (Class) object; this.object = null; } else { this.objectClass = object.getClass(); this.object = object; } this.methodName = methodName; try { Class[] parameters = new Class[ 0 ]; this.method = this.objectClass.getMethod( methodName, parameters ); } catch ( Exception e ) { this.method = null; KoLmafia.updateDisplay( MafiaState.ERROR, "Could not invoke " + this.objectClass + "." + this.methodName ); } this.pauser = new PauseObject(); } public void run() { if ( this.method == null ) { return; } while ( KoLmafia.isRefreshing() ) { this.pauser.pause( 100 ); } try { Object[] args = new Object[ 0 ]; this.method.invoke( this.object, args ); } catch ( Exception e ) { } } } public static final void executeMethod( final Object object, final String method ) { RequestThread.runInParallel( new ExecuteMethodRunnable( object, method ) ); } private static class ExecuteMethodRunnable implements Runnable { private Class objectClass; private Object object; private final String methodName; private Method method; public ExecuteMethodRunnable( final Object object, final String methodName ) { if ( object instanceof Class ) { this.objectClass = (Class) object; this.object = null; } else { this.objectClass = object.getClass(); this.object = object; } this.methodName = methodName; try { Class[] parameters = new Class[ 0 ]; this.method = this.objectClass.getMethod( methodName, parameters ); } catch ( Exception e ) { this.method = null; KoLmafia.updateDisplay( MafiaState.ERROR, "Could not invoke " + this.objectClass + "." + this.methodName ); } } public void run() { if ( this.method == null ) { return; } try { Object[] args = new Object[ 0 ]; this.method.invoke( this.object, args ); } catch ( Exception e ) { } } } /** * Posts a single request one time without forcing concurrency. The display will be enabled if there is no sequence. */ public static final void postRequest( final GenericRequest request ) { if ( request == null ) { return; } // Make sure there is a URL string in the request request.reconstructFields(); boolean force = RequestThread.threadMap.isEmpty() && request.hasResult(); RequestThread.postRequest( force, request ); } public static final void postRequest( final KoLAdventure request ) { if ( request == null ) { return; } boolean force = true; RequestThread.postRequest( force, request ); } public static final void postRequest( final Runnable request ) { if ( request == null ) { return; } boolean force = RequestThread.threadMap.isEmpty(); RequestThread.postRequest( force, request ); } private static final void postRequest( final boolean force, final Runnable request ) { Integer requestId = RequestThread.openRequestSequence( force ); try { if ( Preferences.getBoolean( "debugFoxtrotRemoval" ) && SwingUtilities.isEventDispatchThread() ) { StaticEntity.printStackTrace( "Runnable in event dispatch thread" ); } request.run(); } catch ( Exception e ) { StaticEntity.printStackTrace( e ); } finally { RequestThread.closeRequestSequence( requestId ); } } public static synchronized final void checkOpenRequestSequences( final boolean flush ) { int openSequences = 0; Thread currentThread = Thread.currentThread(); Iterator threadIterator = RequestThread.threadMap.values().iterator(); while ( threadIterator.hasNext() ) { Thread thread = (Thread) threadIterator.next(); if ( thread != currentThread ) { ++openSequences; } } if ( flush ) { RequestThread.threadMap.clear(); KoLmafia.updateDisplay( openSequences + " request sequences will be ignored." ); KoLmafia.enableDisplay(); } else { KoLmafia.updateDisplay( openSequences + " open request sequences detected." ); } StaticEntity.printThreadDump(); } public static synchronized final boolean hasOpenRequestSequences() { return !RequestThread.threadMap.isEmpty(); } public static synchronized final Integer openRequestSequence() { return RequestThread.openRequestSequence( RequestThread.threadMap.isEmpty() ); } public static synchronized final Integer openRequestSequence( final boolean forceContinue ) { if ( forceContinue ) { KoLmafia.forceContinue(); } int requestId = RequestThread.nextRequestId.getAndIncrement(); Integer requestIdObj = IntegerPool.get( requestId ); // Don't include relay requests in "request sequences" - this could stop the display from being enabled // if it ends up being the last thread removed from the threadMap. if ( !StaticEntity.isRelayThread() ) RequestThread.threadMap.put( requestIdObj, Thread.currentThread() ); return requestIdObj; } public static synchronized final void closeRequestSequence( final Integer requestIdObj ) { Thread thread = (Thread) RequestThread.threadMap.remove( requestIdObj ); if ( thread == null || !RequestThread.threadMap.isEmpty() ) { return; } if ( KoLmafia.getLastMessage().endsWith( "..." ) ) { KoLmafia.updateDisplay( "Requests complete." ); SystemTrayFrame.showBalloon( "Requests complete." ); RequestLogger.printLine(); } if ( KoLmafia.permitsContinue() || KoLmafia.refusesContinue() ) { KoLmafia.enableDisplay(); } } /** * Declare world peace. This clears all pending requests and queued * commands and notifies all currently running requests that they * should stop as soon as possible. */ public static final void declareWorldPeace() { StaticEntity.userAborted = true; KoLmafia.updateDisplay( MafiaState.ABORT, "KoLmafia declares world peace." ); KoLmafiaASH.stopAllRelayInterpreters(); InternalMessage message = new InternalMessage( "KoLmafia declares world peace.", "red" ); ChatManager.broadcastEvent( message ); } private static class SequencedRunnable implements Runnable { private final Runnable wrapped; private final boolean sequence; public SequencedRunnable( final Runnable wrapped, final boolean sequence ) { this.wrapped = wrapped; this.sequence = sequence; } public void run() { Integer requestId = null; try { if ( this.sequence ) { requestId = RequestThread.openRequestSequence(); } this.wrapped.run(); } catch ( Exception e ) { StaticEntity.printStackTrace( e ); } finally { if ( requestId != null ) { RequestThread.closeRequestSequence( requestId ); } } } } }