Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.chimerapps.niddler.core.NiddlerDebuggerImpl Maven / Gradle / Ivy
package com.chimerapps.niddler.core;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.chimerapps.niddler.core.debug.NiddlerDebugger;
import com.chimerapps.niddler.util.ConditionVariable;
import com.chimerapps.niddler.util.LogUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import static com.chimerapps.niddler.core.NiddlerDebuggerImpl.DebugAction.extractId;
/**
* @author Nicola Verbeeck
* @version 1
*/
final class NiddlerDebuggerImpl implements NiddlerDebugger {
private static final String TAG = "NiddlerDebugger";
private static final String DEBUG_TYPE_KEY = "controlType";
private static final String DEBUG_PAYLOAD = "payload";
private static final String KEY_MESSAGE_ID = "messageId";
private static final String MESSAGE_ACTIVATE = "activate";
private static final String MESSAGE_DEACTIVATE = "deactivate";
private static final String MESSAGE_MUTE_ACTIONS = "muteActions";
private static final String MESSAGE_UNMUTE_ACTIONS = "unmuteActions";
private static final String MESSAGE_ADD_BLACKLIST = "addBlacklist";
private static final String MESSAGE_REMOVE_BLACKLIST = "removeBlacklist";
private static final String MESSAGE_ADD_DEFAULT_RESPONSE = "addDefaultResponse";
private static final String MESSAGE_DEBUG_REPLY = "debugReply";
private static final String MESSAGE_DEBUG_REQUEST = "debugRequest";
private static final String MESSAGE_ADD_REQUEST = "addRequest";
private static final String MESSAGE_REMOVE_REQUEST = "removeRequest";
private static final String MESSAGE_ADD_RESPONSE = "addResponse";
private static final String MESSAGE_REMOVE_RESPONSE = "removeResponse";
private static final String MESSAGE_ACTIVATE_ACTION = "activateAction";
private static final String MESSAGE_DEACTIVATE_ACTION = "deactivateAction";
private static final String MESSAGE_DELAYS = "updateDelays";
private static final String MESSAGE_ADD_DEFAULT_REQUEST_OVERRIDE = "addDefaultRequestOverride";
private static final String MESSAGE_ADD_REQUEST_OVERRIDE = "addRequestOverride";
private static final String MESSAGE_REMOVE_REQUEST_OVERRIDE = "removeRequestOverride";
private static final long NANO_TO_MILLI = 1000000L;
@NonNull
private final DebuggerConfiguration mDebuggerConfiguration;
@Nullable
private transient ServerConnection mServerConnection;
@NonNull
private final Map> mWaitingResponses;
@NonNull
private final Map> mWaitingRequests;
static int maxStackTraceDepth = 0;
@Nullable
private Runnable mDebuggerConnectionListener;
NiddlerDebuggerImpl() {
mDebuggerConfiguration = new DebuggerConfiguration();
mWaitingResponses = new HashMap<>();
mWaitingRequests = new HashMap<>();
}
void onDebuggerAttached(@NonNull final ServerConnection connection) {
onDebuggerConnectionClosed();
mServerConnection = connection;
}
void onDebuggerConnectionClosed() {
mDebuggerConfiguration.connectionLost();
mServerConnection = null;
synchronized (mWaitingResponses) {
for (final Map.Entry> entry : mWaitingResponses.entrySet()) {
entry.getValue().offer(null);
}
mWaitingResponses.clear();
}
synchronized (mWaitingRequests) {
for (final Map.Entry> entry : mWaitingRequests.entrySet()) {
entry.getValue().offer(null);
}
mWaitingRequests.clear();
}
}
private void onDebuggerConfigurationMessage(@NonNull final String messageType, final JSONObject body, final JSONObject envelopeObject) {
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
LogUtil.niddlerLogVerbose(TAG, String.format("Received debugger message: %s ->\n%s", messageType, envelopeObject));
}
try {
switch (messageType) {
case MESSAGE_ACTIVATE:
mDebuggerConfiguration.setActive(true);
synchronized (mDebuggerConfiguration) {
if (mDebuggerConfiguration.isWaitingForDebugger()) {
mDebuggerConfiguration.setWaitingForDebugger(false);
if (mDebuggerConnectionListener != null) {
mDebuggerConnectionListener.run();
}
mDebuggerConnectionListener = null;
}
}
break;
case MESSAGE_DEACTIVATE:
mDebuggerConfiguration.setActive(false);
break;
case MESSAGE_MUTE_ACTIONS:
mDebuggerConfiguration.muteActions(true);
break;
case MESSAGE_UNMUTE_ACTIONS:
mDebuggerConfiguration.muteActions(false);
break;
case MESSAGE_ADD_BLACKLIST:
mDebuggerConfiguration.addBlacklist(body.getString("regex"));
break;
case MESSAGE_REMOVE_BLACKLIST:
mDebuggerConfiguration.removeBlacklist(body.getString("regex"));
break;
case MESSAGE_ADD_DEFAULT_RESPONSE:
mDebuggerConfiguration.addRequestAction(new DefaultResponseAction(body));
break;
case MESSAGE_DEBUG_REQUEST:
onDebugRequest(envelopeObject.getString(KEY_MESSAGE_ID), parseRequestOverride(body));
break;
case MESSAGE_DEBUG_REPLY:
onDebugResponse(envelopeObject.getString(KEY_MESSAGE_ID), parseResponse(body));
break;
case MESSAGE_ADD_REQUEST:
mDebuggerConfiguration.addRequestAction(new DebugRequestAction(body));
break;
case MESSAGE_REMOVE_REQUEST:
mDebuggerConfiguration.removeRequestAction(extractId(body));
break;
case MESSAGE_ADD_RESPONSE:
mDebuggerConfiguration.addResponseAction(new DebugRequestResponseAction(body));
break;
case MESSAGE_REMOVE_RESPONSE:
mDebuggerConfiguration.removeResponseAction(extractId(body));
break;
case MESSAGE_ACTIVATE_ACTION:
mDebuggerConfiguration.setActionActive(extractId(body), true);
break;
case MESSAGE_DEACTIVATE_ACTION:
mDebuggerConfiguration.setActionActive(extractId(body), false);
break;
case MESSAGE_DELAYS:
mDebuggerConfiguration.updateDelays(body.optLong("preBlacklist"), body.optLong("postBlacklist"), body.optLong("timePerCall"));
break;
case MESSAGE_ADD_DEFAULT_REQUEST_OVERRIDE:
mDebuggerConfiguration.addRequestOverrideAction(new DefaultRequestOverrideAction(body));
break;
case MESSAGE_REMOVE_REQUEST_OVERRIDE:
mDebuggerConfiguration.removeRequestOverrideAction(extractId(body));
break;
case MESSAGE_ADD_REQUEST_OVERRIDE:
mDebuggerConfiguration.addRequestOverrideAction(new DebugRequestOverrideAction(body));
break;
}
} catch (final JSONException e) {
if (LogUtil.isLoggable(TAG, LogUtil.WARN)) {
LogUtil.niddlerLogWarning(TAG, "Invalid debugger json message:", e);
}
}
}
@Override
public boolean isActive() {
return mDebuggerConfiguration.active();
}
@Override
public boolean isBlacklisted(@NonNull final CharSequence url) {
return mDebuggerConfiguration.inBlacklisted(url);
}
@Nullable
@Override
public DebugRequest overrideRequest(@NonNull final NiddlerRequest request) {
final ServerConnection conn = mServerConnection;
if (conn == null) {
return null;
}
try {
final CompletableFuture future = mDebuggerConfiguration.handleRequestOverride(request, this);
if (future == null) {
return null;
}
return future.get();
} catch (final Throwable ignored) {
return null;
}
}
@Nullable
@Override
public DebugResponse handleRequest(@NonNull final NiddlerRequest request) {
final ServerConnection conn = mServerConnection;
if (conn == null) {
return null;
}
try {
final CompletableFuture future = mDebuggerConfiguration.handleRequest(request, this);
if (future == null) {
return null;
}
return future.get();
} catch (final Throwable ignored) {
return null;
}
}
@Nullable
@Override
public DebugResponse handleResponse(@NonNull final NiddlerRequest request, @NonNull final NiddlerResponse response) {
final ServerConnection connection = mServerConnection;
if (connection == null) {
return null;
}
try {
final CompletableFuture future = mDebuggerConfiguration.handleResponse(request, response, this);
if (future == null) {
return null;
}
return future.get();
} catch (final Throwable ignored) {
return null;
}
}
@Override
public boolean applyDelayBeforeBlacklist() throws IOException {
final long timeout = mDebuggerConfiguration.preBlacklistTimeout();
if (timeout <= 0L) {
return false;
}
try {
Thread.sleep(timeout);
return true;
} catch (final InterruptedException e) {
throw new IOException(e);
}
}
@Override
public boolean applyDelayAfterBlacklist() throws IOException {
final long timeout = mDebuggerConfiguration.postBlacklistTimeout();
if (timeout <= 0L) {
return false;
}
try {
Thread.sleep(timeout);
return true;
} catch (final InterruptedException e) {
throw new IOException(e);
}
}
@Override
public boolean ensureCallTime(final long startTime) throws IOException {
final long totalTimeInFlight = (System.nanoTime() - startTime) / NANO_TO_MILLI; //nano to msec
final long minDuration = mDebuggerConfiguration.minimalCallDuration();
final long diff = minDuration - totalTimeInFlight;
if (diff <= 0) {
return false;
}
try {
Thread.sleep(diff);
return true;
} catch (final InterruptedException e) {
throw new IOException(e);
}
}
@Override
public boolean waitForConnection(@NonNull final Runnable onDebuggerConnected) {
synchronized (mDebuggerConfiguration) {
if (mServerConnection != null && mDebuggerConfiguration.active()) {
return false;
}
if (mDebuggerConfiguration.isWaitingForDebugger()) {
return false;
}
mDebuggerConfiguration.setWaitingForDebugger(true);
mDebuggerConnectionListener = onDebuggerConnected;
return true;
}
}
@Override
public boolean isWaitingForConnection() {
synchronized (mDebuggerConfiguration) {
return mDebuggerConfiguration.isWaitingForDebugger();
}
}
@Override
public void cancelWaitForConnection() {
synchronized (mDebuggerConfiguration) {
mDebuggerConnectionListener = null;
mDebuggerConfiguration.setWaitingForDebugger(false);
}
}
void onControlMessage(@NonNull final JSONObject object, final ServerConnection connection) throws JSONException {
if (mServerConnection != connection) {
return;
}
onDebuggerConfigurationMessage(object.getString(DEBUG_TYPE_KEY), object.optJSONObject(DEBUG_PAYLOAD), object);
}
@Nullable
CompletableFuture sendHandleRequest(@NonNull final String actionId, @NonNull final NiddlerRequest request, @Nullable final NiddlerResponse response) {
final ServerConnection connection = mServerConnection;
if (connection == null) {
return null;
}
return DebuggerConfiguration.sendHandleResponse(actionId, request, response, connection, mWaitingResponses);
}
@Nullable
CompletableFuture sendHandleRequestOverride(@NonNull final String actionId, @NonNull final NiddlerRequest request) {
final ServerConnection connection = mServerConnection;
if (connection == null) {
return null;
}
return DebuggerConfiguration.sendHandleRequestOverride(actionId, request, connection, mWaitingRequests);
}
private void onDebugRequest(@NonNull final String messageId, @Nullable final DebugRequest request) {
final CompletableFuture future;
synchronized (mWaitingRequests) {
future = mWaitingRequests.get(messageId);
if (future != null) {
mWaitingRequests.remove(messageId);
}
}
if (future != null) {
future.offer(request);
}
}
private void onDebugResponse(@NonNull final String messageId, @Nullable final DebugResponse response) {
final CompletableFuture future;
synchronized (mWaitingResponses) {
future = mWaitingResponses.get(messageId);
if (future != null) {
mWaitingResponses.remove(messageId);
}
}
if (future != null) {
future.offer(response);
}
}
void onConnectionClosed(final ServerConnection connection) {
if (mServerConnection == connection) {
onDebuggerConnectionClosed();
}
}
static final class DebuggerConfiguration {
private final ReentrantReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(false);
private final Lock mWriteLock = mReadWriteLock.writeLock();
private final Lock mReadLock = mReadWriteLock.readLock();
private final List mBlacklist = new ArrayList<>();
private final List mRequestOverrideActions = new ArrayList<>();
private final List mRequestActions = new ArrayList<>();
private final List mResponseActions = new ArrayList<>();
private boolean mIsActive = false;
private boolean mActionsMuted = false;
private long mPreBlacklistTimeout = 0L;
private long mPostBlacklistTimeout = 0L;
private long mTimePerCall = 0L;
private boolean mWaitingForDebugger;
private final ConditionVariable mWaitingForDebuggerVariable = new ConditionVariable();
DebuggerConfiguration() {
mWaitingForDebuggerVariable.open();
}
boolean active() {
try {
mReadLock.lock();
return mIsActive;
} finally {
mReadLock.unlock();
}
}
boolean isWaitingForDebugger() {
try {
mReadLock.lock();
return mWaitingForDebugger;
} finally {
mReadLock.unlock();
}
}
void setWaitingForDebugger(boolean waiting) {
try {
mWriteLock.lock();
mWaitingForDebugger = waiting;
if (mWaitingForDebugger) {
mWaitingForDebuggerVariable.close();
} else {
mWaitingForDebuggerVariable.open();
}
} finally {
mWriteLock.unlock();
}
}
void muteActions(final boolean muted) {
mWriteLock.lock();
mActionsMuted = muted;
mWriteLock.unlock();
}
void connectionLost() {
try {
mWriteLock.lock();
mBlacklist.clear();
mRequestActions.clear();
mResponseActions.clear();
mRequestOverrideActions.clear();
mIsActive = false;
} finally {
mWriteLock.unlock();
}
}
void addBlacklist(@NonNull final String regex) {
mWriteLock.lock();
if (mIsActive) {
mBlacklist.add(Pattern.compile(regex));
}
mWriteLock.unlock();
}
void removeBlacklist(@NonNull final String regex) {
mWriteLock.lock();
final Iterator it = mBlacklist.iterator();
while (it.hasNext()) {
if (it.next().pattern().equals(regex)) {
it.remove();
}
}
mWriteLock.unlock();
}
boolean inBlacklisted(@NonNull final CharSequence url) {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
if (mIsActive) {
for (final Pattern pattern : mBlacklist) {
if (pattern.matcher(url).matches()) {
return true;
}
}
}
return false;
} finally {
mReadLock.unlock();
}
}
void addRequestAction(@NonNull final RequestAction action) {
mWriteLock.lock();
mRequestActions.add(action);
mWriteLock.unlock();
}
void removeRequestAction(@NonNull final String actionId) {
mWriteLock.lock();
final Iterator it = mRequestActions.iterator();
while (it.hasNext()) {
if (it.next().id.equals(actionId)) {
it.remove();
}
}
mWriteLock.unlock();
}
void addResponseAction(@NonNull final ResponseAction action) {
mWriteLock.lock();
mResponseActions.add(action);
mWriteLock.unlock();
}
void removeResponseAction(@NonNull final String actionId) {
mWriteLock.lock();
final Iterator it = mResponseActions.iterator();
while (it.hasNext()) {
if (it.next().id.equals(actionId)) {
it.remove();
}
}
mWriteLock.unlock();
}
void addRequestOverrideAction(@NonNull final RequestOverrideAction action) {
mWriteLock.lock();
mRequestOverrideActions.add(action);
mWriteLock.unlock();
}
void removeRequestOverrideAction(@NonNull final String actionId) {
mWriteLock.lock();
final Iterator it = mRequestOverrideActions.iterator();
while (it.hasNext()) {
if (it.next().id.equals(actionId)) {
it.remove();
}
}
mWriteLock.unlock();
}
void setActionActive(@NonNull final String actionId, final boolean isActive) {
mWriteLock.lock();
for (final ResponseAction responseAction : mResponseActions) {
if (responseAction.id.equals(actionId)) {
responseAction.active = isActive;
}
}
for (final RequestAction requestAction : mRequestActions) {
if (requestAction.id.equals(actionId)) {
requestAction.active = isActive;
}
}
mWriteLock.unlock();
}
@Nullable
CompletableFuture handleRequestOverride(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) throws IOException {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
if (mIsActive && !mActionsMuted) {
for (final RequestOverrideAction requestOverrideAction : mRequestOverrideActions) {
final CompletableFuture response = requestOverrideAction.handleRequestOverride(request, debugger);
if (response != null) {
return response;
}
}
}
return null;
} finally {
mReadLock.unlock();
}
}
@Nullable
CompletableFuture handleRequest(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) throws IOException {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
if (mIsActive && !mActionsMuted) {
for (final RequestAction requestAction : mRequestActions) {
final CompletableFuture response = requestAction.handleRequest(request, debugger);
if (response != null) {
return response;
}
}
}
return null;
} finally {
mReadLock.unlock();
}
}
@Nullable
CompletableFuture handleResponse(@NonNull final NiddlerRequest request,
@NonNull final NiddlerResponse response,
final NiddlerDebuggerImpl niddlerDebugger) throws IOException {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
if (mIsActive && !mActionsMuted) {
for (final ResponseAction responseAction : mResponseActions) {
final CompletableFuture serverResponse = responseAction.handleResponse(request, response, niddlerDebugger);
if (serverResponse != null) {
return serverResponse;
}
}
}
return null;
} finally {
mReadLock.unlock();
}
}
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
static CompletableFuture sendHandleResponse(@NonNull final String actionId,
@NonNull final NiddlerRequest request,
@Nullable final NiddlerResponse response,
@NonNull final ServerConnection connection,
@NonNull final Map> waitingResponses) {
final CompletableFuture future = new CompletableFuture<>();
synchronized (waitingResponses) {
waitingResponses.put(request.getMessageId(), future);
}
try {
connection.send(makeDebugRequestMessage(actionId, request, response));
} catch (final JSONException e) {
future.offer(null);
if (LogUtil.isLoggable(TAG, LogUtil.WARN)) {
LogUtil.niddlerLogWarning(TAG, "Failed to offer:", e);
}
}
return future;
}
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
static CompletableFuture sendHandleRequestOverride(@NonNull final String actionId,
@NonNull final NiddlerRequest request,
@NonNull final ServerConnection connection,
@NonNull final Map> waitingRequests) {
final CompletableFuture future = new CompletableFuture<>();
synchronized (waitingRequests) {
waitingRequests.put(request.getMessageId(), future);
}
try {
connection.send(makeDebugRequestOverrideMessage(actionId, request));
} catch (final JSONException e) {
future.offer(null);
if (LogUtil.isLoggable(TAG, LogUtil.WARN)) {
LogUtil.niddlerLogWarning(TAG, "Failed to offer:", e);
}
}
return future;
}
long preBlacklistTimeout() {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
return mIsActive ? mPreBlacklistTimeout : 0L;
} finally {
mReadLock.unlock();
}
}
long postBlacklistTimeout() {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
return mIsActive ? mPostBlacklistTimeout : 0L;
} finally {
mReadLock.unlock();
}
}
long minimalCallDuration() {
mWaitingForDebuggerVariable.block();
try {
mReadLock.lock();
return mIsActive ? mTimePerCall : 0L;
} finally {
mReadLock.unlock();
}
}
void updateDelays(@Nullable final Long preBlacklist, @Nullable final Long postBlacklist, @Nullable final Long timePerCall) {
try {
mWriteLock.lock();
mPreBlacklistTimeout = preBlacklist == null ? 0L : preBlacklist;
mPostBlacklistTimeout = postBlacklist == null ? 0L : postBlacklist;
mTimePerCall = timePerCall == null ? 0L : timePerCall;
} finally {
mWriteLock.unlock();
}
}
void setActive(final boolean active) {
try {
mWriteLock.lock();
mIsActive = active;
} finally {
mWriteLock.unlock();
}
}
}
static abstract class DebugAction {
@NonNull
final String id;
final int repeatCount;
final AtomicInteger callCount;
transient boolean active;
DebugAction(@NonNull final String id, final boolean active, final int repeatCount) {
this.id = id;
this.active = active;
this.repeatCount = repeatCount;
callCount = new AtomicInteger(0);
}
@NonNull
static String extractId(@NonNull final JSONObject object) throws JSONException {
return object.getString("id");
}
static boolean extractActiveState(@NonNull final JSONObject object) {
return object.optBoolean("active", true);
}
static int extractRepeatCount(@NonNull final JSONObject object) {
return object.optInt("repeatCount", -1);
}
@Nullable
static String extractMatchingRegex(final JSONObject object) {
return object.optString("regex", null);
}
}
static abstract class RequestOverrideAction extends DebugAction {
RequestOverrideAction(@NonNull final String id, final boolean active, final int repeatCount) {
super(id, active, repeatCount);
}
@Nullable
abstract CompletableFuture handleRequestOverride(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) throws IOException;
}
static abstract class RequestAction extends DebugAction {
RequestAction(@NonNull final String id, final boolean active, final int repeatCount) {
super(id, active, repeatCount);
}
@Nullable
abstract CompletableFuture handleRequest(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) throws IOException;
}
static abstract class ResponseAction extends DebugAction {
ResponseAction(@NonNull final String id, final boolean active, final int repeatCount) {
super(id, active, repeatCount);
}
@Nullable
abstract CompletableFuture handleResponse(@NonNull final NiddlerRequest request,
@NonNull final NiddlerResponse response,
@NonNull final NiddlerDebuggerImpl debugger) throws IOException;
}
static final class DefaultResponseAction extends RequestAction {
@Nullable
private final Pattern mRegex;
@Nullable
private final String mMethod;
@NonNull
private final DebugResponse mDebugResponse;
DefaultResponseAction(final JSONObject object) throws JSONException {
super(extractId(object), extractActiveState(object), extractRepeatCount(object));
final String regexString = extractMatchingRegex(object);
mRegex = regexString == null ? null : Pattern.compile(regexString);
mMethod = object.optString("matchMethod", null);
mDebugResponse = parseResponse(object);
}
@Nullable
@Override
CompletableFuture handleRequest(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) {
if (!active) {
return null;
}
if (mRegex != null && !mRegex.matcher(request.getUrl()).matches()) {
return null;
}
if (mMethod != null && !mMethod.equalsIgnoreCase(request.getMethod())) {
return null;
}
if (repeatCount > 0 && callCount.incrementAndGet() >= repeatCount) {
return null;
}
return new CompletableFuture<>(mDebugResponse);
}
}
static final class DebugRequestAction extends RequestAction {
@Nullable
private final Pattern mRegex;
@Nullable
private final String mMethod;
DebugRequestAction(final JSONObject object) throws JSONException {
super(extractId(object), extractActiveState(object), extractRepeatCount(object));
final String regexString = extractMatchingRegex(object);
mRegex = regexString == null ? null : Pattern.compile(regexString);
mMethod = object.optString("matchMethod", null);
}
@Nullable
@Override
CompletableFuture handleRequest(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) {
if (!active) {
return null;
}
if (mRegex != null && !mRegex.matcher(request.getUrl()).matches()) {
return null;
}
if (mMethod != null && !mMethod.equalsIgnoreCase(request.getMethod())) {
return null;
}
if (repeatCount > 0 && callCount.incrementAndGet() >= repeatCount) {
return null;
}
return debugger.sendHandleRequest(id, request, null);
}
}
static final class DebugRequestResponseAction extends ResponseAction {
@Nullable
private final Pattern mRegex;
@Nullable
private final String mMethod;
@Nullable
private final Integer mResponseCode;
DebugRequestResponseAction(final JSONObject object) throws JSONException {
super(extractId(object), extractActiveState(object), extractRepeatCount(object));
final String regexString = extractMatchingRegex(object);
mRegex = regexString == null ? null : Pattern.compile(regexString);
mMethod = object.optString("matchMethod", null);
mResponseCode = object.has("responseCode") ? object.optInt("responseCode") : null;
}
@Nullable
@Override
CompletableFuture handleResponse(@NonNull final NiddlerRequest request,
@NonNull final NiddlerResponse response,
@NonNull final NiddlerDebuggerImpl debugger) {
if (!active) {
return null;
}
if (mRegex != null && !mRegex.matcher(request.getUrl()).matches()) {
return null;
}
if (mMethod != null && !mMethod.equalsIgnoreCase(request.getMethod())) {
return null;
}
if (mResponseCode != null) {
final Integer code = response.getStatusCode();
if (code != null && ((int) mResponseCode) != code) {
return null;
}
}
if (repeatCount > 0 && callCount.incrementAndGet() >= repeatCount) {
return null;
}
return debugger.sendHandleRequest(id, request, response);
}
}
static final class DefaultRequestOverrideAction extends RequestOverrideAction {
@Nullable
private final Pattern mRegex;
@Nullable
private final String mMethod;
@NonNull
private final DebugRequest mDebugRequest;
DefaultRequestOverrideAction(final JSONObject object) throws JSONException {
super(extractId(object), extractActiveState(object), extractRepeatCount(object));
final String regexString = extractMatchingRegex(object);
mRegex = regexString == null ? null : Pattern.compile(regexString);
mMethod = object.optString("matchMethod", null);
mDebugRequest = parseRequestOverride(object);
}
@Nullable
@Override
CompletableFuture handleRequestOverride(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) {
if (!active) {
return null;
}
if (mRegex != null && !mRegex.matcher(request.getUrl()).matches()) {
return null;
}
if (mMethod != null && !mMethod.equalsIgnoreCase(request.getMethod())) {
return null;
}
if (repeatCount > 0 && callCount.incrementAndGet() >= repeatCount) {
return null;
}
return new CompletableFuture<>(mDebugRequest);
}
}
static final class DebugRequestOverrideAction extends RequestOverrideAction {
@Nullable
private final Pattern mRegex;
@Nullable
private final String mMethod;
DebugRequestOverrideAction(final JSONObject object) throws JSONException {
super(extractId(object), extractActiveState(object), extractRepeatCount(object));
final String regexString = extractMatchingRegex(object);
mRegex = regexString == null ? null : Pattern.compile(regexString);
mMethod = object.optString("matchMethod", null);
}
@Nullable
@Override
CompletableFuture handleRequestOverride(@NonNull final NiddlerRequest request, @NonNull final NiddlerDebuggerImpl debugger) {
if (!active) {
return null;
}
if (mRegex != null && !mRegex.matcher(request.getUrl()).matches()) {
return null;
}
if (mMethod != null && !mMethod.equalsIgnoreCase(request.getMethod())) {
return null;
}
if (repeatCount > 0 && callCount.incrementAndGet() >= repeatCount) {
return null;
}
return debugger.sendHandleRequestOverride(id, request);
}
}
@Nullable
static DebugResponse parseResponse(@Nullable final JSONObject config) throws JSONException {
if (config == null) {
return null;
}
return new DebugResponse(config.getInt("code"),
config.getString("message"),
parseHeaders(config.optJSONObject("headers")),
config.optString("encodedBody", null),
config.optString("bodyMimeType", null));
}
@NonNull
static DebugRequest parseRequestOverride(@Nullable final JSONObject config) throws JSONException {
if (config == null) {
return null;
}
return new DebugRequest(config.getString("url"),
config.getString("method"),
parseHeaders(config.optJSONObject("headers")),
config.optString("encodedBody", null),
config.optString("bodyMimeType", null));
}
@Nullable
private static Map> parseHeaders(@Nullable final JSONObject headersObject) throws JSONException {
if (headersObject == null) {
return null;
}
final Map> headers = new HashMap<>();
@SuppressWarnings("unchecked") final Iterator keys = headersObject.keys();
while (keys.hasNext()) {
final String key = keys.next();
final JSONArray array = headersObject.getJSONArray(key);
final int numItems = array.length();
final List headersForKey = new ArrayList<>(numItems);
for (int i = 0; i < numItems; ++i) {
headersForKey.add(array.getString(i));
}
headers.put(key, headersForKey);
}
return headers;
}
@NonNull
static String makeDebugRequestMessage(@NonNull final String actionId, @NonNull final NiddlerRequest request, @Nullable final NiddlerResponse response) throws JSONException {
final JSONObject object = new JSONObject();
object.put("type", "debugRequest");
object.put("requestId", request.getMessageId());
object.put("actionId", actionId);
if (response != null) {
object.put("response", MessageBuilder.buildMessageJson(response));
}
return object.toString();
}
@NonNull
static String makeDebugRequestOverrideMessage(@NonNull final String actionId, @NonNull final NiddlerRequest request) throws JSONException {
final JSONObject requestObj = MessageBuilder.buildMessageJson(request, maxStackTraceDepth);
final JSONObject object = new JSONObject();
object.put("type", "debugRequest");
object.put("actionId", actionId);
object.put("request", requestObj);
return object.toString();
}
static class CompletableFuture implements Future {
private final BlockingQueue> reply = new ArrayBlockingQueue<>(1);
private volatile boolean done = false;
CompletableFuture() {
}
CompletableFuture(T value) {
try {
reply.put(new OptionalWrapper<>(value));
} catch (final InterruptedException ignored) {
}
}
void offer(final T value) {
try {
done = true;
reply.put(new OptionalWrapper<>(value));
} catch (final InterruptedException ignored) {
if (LogUtil.isLoggable(TAG, LogUtil.WARN)) {
LogUtil.niddlerLogWarning(TAG, "Failed to offer:", ignored);
}
}
}
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return done;
}
@Override
public T get() throws InterruptedException {
return reply.take().value;
}
@Override
public T get(final long timeout, @NonNull final TimeUnit unit) throws InterruptedException, TimeoutException {
final OptionalWrapper replyOrNull = reply.poll(timeout, unit);
if (replyOrNull == null) {
throw new TimeoutException();
}
return replyOrNull.value;
}
}
static class OptionalWrapper {
T value;
OptionalWrapper(T value) {
this.value = value;
}
}
}