/*
* jBrowserDriver (TM)
* Copyright (C) 2014-2016 Machine Publishers, LLC and the jBrowserDriver contributors
* https://github.com/MachinePublishers/jBrowserDriver
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.machinepublishers.jbrowserdriver;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.openqa.selenium.TimeoutException;
import com.google.common.util.concurrent.UncheckedExecutionException;
import javafx.application.Platform;
class AppThread {
private static final Random rand = new Random();
static interface Sync<T> {
T perform();
}
private static class Runner<T> implements Runnable {
private static final int MAX_DELAY = 500;
private final Sync<T> action;
private final StatusCode statusCode;
private final AtomicBoolean done;
private final AtomicReference<T> returned;
private final int delay;
private final AtomicBoolean cancel;
private final AtomicReference<Throwable> failure;
public Runner(Sync<T> action, StatusCode statusCode) {
this.action = action;
this.statusCode = statusCode;
this.done = new AtomicBoolean();
this.returned = new AtomicReference<T>();
this.delay = 1;
this.cancel = new AtomicBoolean();
this.failure = new AtomicReference<Throwable>();
}
public Runner(Runner other) {
this.action = other.action;
this.statusCode = other.statusCode;
this.done = other.done;
this.returned = other.returned;
this.delay = Math.min(MAX_DELAY, other.delay * 2);
this.cancel = other.cancel;
this.failure = other.failure;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
if (!cancel.get()) {
synchronized (statusCode) {
if (statusCode.get() == 0) {
final Runner newRunner = new Runner(this);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {}
Platform.runLater(newRunner);
}
}).start();
} else {
if (statusCode.get() > 299) {
LogsServer.instance().trace("Performing browser action, but HTTP status is " + statusCode.get() + ".");
}
T result = null;
try {
result = action.perform();
} catch (Throwable t) {
failure.set(t);
} finally {
synchronized (done) {
returned.set(result);
done.set(true);
done.notifyAll();
}
}
}
}
}
}
}
private static void pause() {
AppThread.exec(new Sync<Object>() {
@Override
public Object perform() {
try {
Thread.sleep(30 + rand.nextInt(40));
} catch (Throwable t) {}
return null;
}
});
}
static void handleExecutionException(Object obj) {
if (obj instanceof UncheckedExecutionException) {
throw (UncheckedExecutionException) obj;
}
if (obj instanceof RuntimeException) {
throw new UncheckedExecutionException((RuntimeException) obj);
}
if (obj instanceof Throwable) {
throw new UncheckedExecutionException(new RuntimeException((Throwable) obj));
}
}
static <T> T exec(final Sync<T> action) {
return exec(false, new StatusCode(), 0, action);
}
static <T> T exec(final StatusCode statusCode, final Sync<T> action) {
return exec(false, statusCode, 0, action);
}
static <T> T exec(final boolean pauseAfterExec, final StatusCode statusCode, final Sync<T> action) {
return exec(pauseAfterExec, statusCode, 0, action);
}
static <T> T exec(final StatusCode statusCode, final long timeout, final Sync<T> action) {
return exec(false, statusCode, timeout, action);
}
static <T> T exec(final boolean pauseAfterExec, final StatusCode statusCode, final long timeout,
final Sync<T> action) {
try {
if ((boolean) Platform.isFxApplicationThread()) {
return action.perform();
}
final Runner<T> runner = new Runner<T>(action, statusCode);
synchronized (runner.done) {
Platform.runLater(runner);
}
synchronized (runner.done) {
if (!runner.done.get()) {
try {
runner.done.wait(timeout);
} catch (InterruptedException e) {
LogsServer.instance().exception(e);
}
if (!runner.done.get()) {
runner.cancel.set(true);
throw new TimeoutException(new StringBuilder()
.append("Timeout of ").append(timeout).append("ms reached.").toString());
}
}
handleExecutionException(runner.failure.get());
return runner.returned.get();
}
} finally {
if (pauseAfterExec) {
pause();
}
}
}
}