/*
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the Classpath exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.api.impl;
import com.sun.btrace.comm.Command;
import java.io.File;
import java.io.PrintWriter;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.btrace.api.BTraceEngine;
import com.sun.btrace.api.BTraceTask;
import com.sun.btrace.comm.ErrorCommand;
import com.sun.btrace.comm.GridDataCommand;
import com.sun.btrace.comm.MessageCommand;
import com.sun.btrace.comm.NumberDataCommand;
import com.sun.btrace.comm.NumberMapDataCommand;
import com.sun.btrace.comm.RetransformClassNotification;
import com.sun.btrace.comm.StringMapDataCommand;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author Jaroslav Bachorik
*/
public class BTraceTaskImpl extends BTraceTask implements BTraceEngineImpl.StateListener {
final private static Pattern NAMED_EVENT_PATTERN = Pattern.compile("@OnEvent\\s*\\(\\s*\\\"(\\w.*)\\\"\\s*\\)", Pattern.MULTILINE);
final private static Pattern ANONYMOUS_EVENT_PATTERN = Pattern.compile("@OnEvent(?!\\s*\\()");
final private static Pattern TRUSTED_PATTERN = Pattern.compile("@BTrace\\s*\\(.*(unsafe|trusted)\\s*=\\s*(true|false).*\\)", Pattern.MULTILINE);
final private static Pattern NAME_ANNOTATION_PATTERN = Pattern.compile("@BTrace\\s*\\(.*name\\s*=\\s*\"(.*)\".*\\)", Pattern.MULTILINE);
final private static Pattern NAME_PATTERN = Pattern.compile("@BTrace.*?class\\s+(.*?)\\s+", Pattern.MULTILINE);
final private AtomicReference<State> currentState = new AtomicReference<>(State.NEW);
final private Set<StateListener> stateListeners = new HashSet<>();
final private Set<MessageDispatcher> messageDispatchers = new HashSet<>();
final private static ExecutorService dispatcher = Executors.newSingleThreadExecutor();
private String script;
private int numInstrClasses;
private boolean trusted;
final private BTraceEngineImpl engine;
final private int pid;
public BTraceTaskImpl(int pid, BTraceEngine engine) {
this.pid = pid;
this.engine = (BTraceEngineImpl)engine;
this.engine.addListener(this);
}
@Override
public String getScript() {
return script != null ? script : "";
}
@Override
public String getName() {
Matcher m = NAME_ANNOTATION_PATTERN.matcher(getScript());
if (m.find()) {
return m.group(1);
} else {
m = NAME_PATTERN.matcher(getScript());
if (m.find()) {
return m.group(1);
}
}
return null;
}
@Override
public void setScript(String newValue) {
script = newValue;
Matcher m = TRUSTED_PATTERN.matcher(getScript());
if (m.find()) {
trusted = Boolean.parseBoolean(m.group(1));
}
}
@Override
public void sendEvent(String event) {
engine.sendEvent(this, event);
}
@Override
public void sendEvent() {
engine.sendEvent(this);
}
@Override
public Set<String> getNamedEvents() {
Set<String> events = new HashSet<String>();
Matcher matcher = NAMED_EVENT_PATTERN.matcher(getScript());
while (matcher.find()) {
events.add(matcher.group(1));
}
return events;
}
@Override
public boolean hasAnonymousEvents() {
Matcher matcher = ANONYMOUS_EVENT_PATTERN.matcher(getScript());
return matcher.find();
}
@Override
public boolean hasEvents() {
return getScript() != null && getScript().contains("@OnEvent");
}
@Override
public int getPid() {
return pid;
}
/**
* Property getter
* @return Returns the current state
*/
protected State getState() {
return currentState.get();
}
@Override
public int getInstrClasses() {
return EnumSet.of(State.INSTRUMENTING, State.RUNNING).contains(getState()) ? numInstrClasses : -1;
}
/**
* Listener management (can use {@linkplain WeakListeners} to create a new listener)
* @param listener {@linkplain StateListener} instance to add
*/
@Override
public void addStateListener(StateListener listener) {
synchronized (stateListeners) {
stateListeners.add(listener);
}
}
/**
* Listener management
* @param listener {@linkplain StateListener} instance to remove
*/
@Override
public void removeStateListener(StateListener listener) {
synchronized (stateListeners) {
stateListeners.remove(listener);
}
}
/**
* Dispatcher management (can use {@linkplain WeakListeners} to create a new listener)
* @param dispatcher {@linkplain StateListener} instance to add
*/
@Override
public void addMessageDispatcher(MessageDispatcher dispatcher) {
synchronized (messageDispatchers) {
messageDispatchers.add(dispatcher);
}
}
/**
* Dispatcher management
* @param dispatcher {@linkplain StateListener} instance to remove
*/
@Override
public void removeMessageDispatcher(MessageDispatcher dispatcher) {
synchronized (messageDispatchers) {
messageDispatchers.remove(dispatcher);
}
}
/**
* Starts the injected code using the attached {@linkplain BTraceEngine}
*/
@Override
public void start() {
setState(State.STARTING);
if (!engine.start(this)) {
setState(State.FAILED);
}
}
/**
* Stops the injected code running on the attached {@linkplain BTraceEngine}
*/
@Override
public void stop() {
if (getState() == State.RUNNING) {
if (!engine.stop(this)) {
setState(State.RUNNING);
}
}
}
/**
* @see BTraceEngine.StateListener#onTaskStart(net.java.visualvm.btrace.api.BTraceTask)
*/
@Override
public void onTaskStart(BTraceTask task) {
if (task.equals(this)) {
setState(State.RUNNING);
}
}
/**
* @see BTraceEngine.StateListener#onTaskStop(net.java.visualvm.btrace.api.BTraceTask)
*/
@Override
public void onTaskStop(BTraceTask task) {
if (task.equals(this)) {
setState(State.FINISHED);
}
}
@Override
public String getClassPath() {
StringBuilder sb = new StringBuilder();
for(String cpEntry : engine.getClasspathProvider().getClasspath(this)) {
if (sb.length() > 0) {
sb.append(File.pathSeparator);
}
sb.append(cpEntry);
}
return sb.toString();
}
@Override
public boolean isTrusted() {
return trusted;
}
void setState(State newValue) {
currentState.set(newValue);
fireStateChange();
}
void setInstrClasses(int value) {
numInstrClasses = value;
}
private void fireStateChange() {
Set<StateListener> toProcess = null;
synchronized (stateListeners) {
toProcess = new HashSet<StateListener>(stateListeners);
}
for (StateListener listener : toProcess) {
listener.stateChanged(getState());
}
}
@SuppressWarnings("FutureReturnValueIgnored")
void dispatchCommand(final Command cmd) {
final Set<MessageDispatcher> dispatchingSet = new HashSet<BTraceTask.MessageDispatcher>();
synchronized(messageDispatchers) {
dispatchingSet.addAll(messageDispatchers);
}
dispatcher.submit(new Runnable() {
@Override
public void run() {
for(MessageDispatcher listener : dispatchingSet) {
switch (cmd.getType()) {
case Command.MESSAGE: {
listener.onPrintMessage(((MessageCommand)cmd).getMessage());
break;
}
case Command.RETRANSFORM_CLASS: {
listener.onClassInstrumented(((RetransformClassNotification)cmd).getClassName());
break;
}
case Command.NUMBER: {
NumberDataCommand ndc = (NumberDataCommand)cmd;
listener.onNumberMessage(ndc.getName(), ndc.getValue());
break;
}
case Command.NUMBER_MAP: {
NumberMapDataCommand nmdc = (NumberMapDataCommand)cmd;
listener.onNumberMap(nmdc.getName(), nmdc.getData());
break;
}
case Command.STRING_MAP: {
StringMapDataCommand smdc = (StringMapDataCommand)cmd;
listener.onStringMap(smdc.getName(), smdc.getData());
break;
}
case Command.GRID_DATA: {
GridDataCommand gdc = (GridDataCommand)cmd;
listener.onGrid(gdc.getName(), gdc.getData());
break;
}
case Command.ERROR: {
ErrorCommand ec = (ErrorCommand)cmd;
listener.onError(ec.getCause());
break;
}
}
}
}
});
}
@Override
@SuppressWarnings("ReferenceEquality")
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BTraceTaskImpl other = (BTraceTaskImpl) obj;
if (this.pid != other.pid) {
return false;
}
return !(this.getName() != other.getName() && (this.getName() == null || !this.getName().equals(other.getName())));
}
@Override
public int hashCode() {
int hash = 7;
hash = 19 * hash + pid;
hash = 19 * hash + (this.getName() != null ? this.getName().hashCode() : 0);
return hash;
}
}