blob: 8fb58ef2e664f3d8a730148aff81616940038178 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2017, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.server.rh;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.rj.server.dbg.Srcfiles.equalsTimestamp;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.LOGGER;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.containsPositionsById;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.equalsPositions;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.filterPositionsById;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.filterPositionsByIdRequired;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.findIndexByReference;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.findIndexBySrcref;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.getSrcrefForIndex;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.indexOfTracepointById;
import static org.eclipse.statet.rj.server.dbg.Tracepoints.rebasePositionsByIndex;
import static org.eclipse.statet.rj.server.rh.ObjectManager.WEAK_REF;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.IdentitySet;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.collections.SortedArraySet;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.lang.ObjectUtils;
import org.eclipse.statet.rj.server.RjsException;
import org.eclipse.statet.rj.server.RjsStatus;
import org.eclipse.statet.rj.server.dbg.DbgListener;
import org.eclipse.statet.rj.server.dbg.ElementTracepointInstallationRequest;
import org.eclipse.statet.rj.server.dbg.ElementTracepoints;
import org.eclipse.statet.rj.server.dbg.FlagTracepointInstallationRequest;
import org.eclipse.statet.rj.server.dbg.SrcfileData;
import org.eclipse.statet.rj.server.dbg.Srcref;
import org.eclipse.statet.rj.server.dbg.Tracepoint;
import org.eclipse.statet.rj.server.dbg.TracepointEvent;
import org.eclipse.statet.rj.server.dbg.TracepointPosition;
import org.eclipse.statet.rj.server.dbg.TracepointState;
import org.eclipse.statet.rj.server.dbg.TracepointStatesUpdate;
import org.eclipse.statet.rj.server.dbg.Tracepoints;
import org.eclipse.statet.rj.server.rh.ObjectManager.EnvListener;
@NonNullByDefault
public abstract class AbstractTracepointManager {
protected static final String TOPLEVEL_ELEMENT_ID= "200:"; //$NON-NLS-1$
private static final String ENV_INSTALLED_TAG= "#" + WEAK_REF + ":dbg.tracepoints";
private static final int MAX_HISTORY= 12;
private static final int MAX_FUZZY= 8;
protected static final class StateSet extends SortedArraySet<@NonNull StateEntry> {
public StateSet(final int initialSize) {
super(new @NonNull StateEntry[initialSize], 0, null);
}
protected static final int binarySearchId(final StateEntry[] a, int begin, int end, final long id) {
end--;
while (begin <= end) {
final int i= (begin + end) >>> 1;
final int d= a[i].compareToId(id);
if (d < 0) {
begin= i + 1;
}
else if (d > 0) {
end= i - 1;
}
else {
return i;
}
}
return -(begin + 1);
}
public int indexOfId(final long id) {
return binarySearchId(array(), 0, size(), id);
}
}
private static final boolean containsCommonElement(final List<? extends ElementTracepoints> list) {
for (final ElementTracepoints element : list) {
if (!element.getElementId().equals(TOPLEVEL_ELEMENT_ID)) {
return true;
}
}
return false;
}
private static final int indexOfStateEntry(final List<StateEntry> entries,
final long id) {
for (int idx= 0; idx < entries.size(); idx++) {
if (entries.get(idx).getId() == id) {
return idx;
}
}
return -1;
}
protected static final @Nullable StateEntry getBreakpointState(final StateSet states,
final long id) {
final int idx= states.indexOfId(id);
if (idx >= 0) {
final StateEntry entry= states.get(idx);
final TracepointState state= entry.getState();
if (state != null
&& (state.getType() & Tracepoint.TYPE_BREAKPOINT) != 0) {
return entry;
}
}
return null;
}
protected static final @Nullable StateEntry getBreakpointState(final StateSet states,
final @Nullable String elementId, final int @Nullable [] index) {
if (elementId != null && index != null) {
for (int i= 0; i < states.size(); i++) {
final StateEntry entry= states.get(i);
final TracepointState state= entry.getState();
if (state != null
&& (state.getType() & Tracepoint.TYPE_BREAKPOINT) != 0
&& elementId.equals(state.getElementId())
&& Arrays.equals(index, state.getIndex()) ) {
return entry;
}
}
}
return null;
}
private static final boolean isAllDeleted(final List<? extends TracepointPosition> positions,
final StateSet states) {
return false;
// synchronized (states) {
// for (final TracepointPosition position : positions) {
// final int idx= states.indexOfId(position.getId());
// if (idx >= 0 && states.get(idx).getState() != null) {
// return false;
// }
// }
// }
// return true;
}
protected static final ImList<TracepointPosition> NO_POSITIONS= ImCollections.emptyList();
protected static final ElementTracepoints NO_POSITION_ELEMENT_TRACEPOINTS= new ElementTracepoints(
new SrcfileData(null, null, 0), "", null, NO_POSITIONS ); //$NON-NLS-1$
protected static final ElementTracepoints NO_CHANGE_ELEMENT_TRACEPOINTS= new ElementTracepoints(
new SrcfileData(null, null, 0), "", null, NO_POSITIONS ); //$NON-NLS-1$
/**
* Collection of (best matching) element tracepoints for current tracepoint positions.
*/
protected final static class ElementTracepointsCollection {
private final long updatingStamp;
private final ElementTracepoints current;
private final List<ElementTracepoints> list;
private final long requestedTimestamp;
public ElementTracepointsCollection(final long updatingStamp,
final ElementTracepoints current, final List<ElementTracepoints> list,
final long requestedTimestamp) {
this.updatingStamp= updatingStamp;
this.current= current;
this.list= list;
this.requestedTimestamp= requestedTimestamp;
}
public ElementTracepointsCollection(final long updatingStamp,
final ElementTracepoints current, final ElementTracepoints single,
final long requestedTimestamp) {
this.updatingStamp= updatingStamp;
this.current= current;
this.list= ImCollections.newList(single);
this.requestedTimestamp= requestedTimestamp;
}
public long getUpdatingStamp() {
return this.updatingStamp;
}
public ElementTracepoints getCurrent() {
return this.current;
}
public List<ElementTracepoints> getList() {
return this.list;
}
public boolean isActualPositionsEmpty() {
return this.current.getPositions().isEmpty();
}
public List<? extends TracepointPosition> getActualPositions(final List<? extends TracepointPosition> positions) {
return filterPositionsByIdRequired(positions, this.current.getPositions());
}
public long getRequestedTimestamp() {
return this.requestedTimestamp;
}
}
protected static class PathEntry {
private final String path;
private long updateStamp;
private @Nullable SrcfileData srcfile;
private final List<ElementEntry> elementEntries= new ArrayList<>();
private final StateSet states= new StateSet(8);
private final List<ElementTracepointsCollection> updatingInstalled= new ArrayList<>();
public PathEntry(final String path) {
this.path= path;
}
public final String getPath() {
return this.path;
}
public final long getUpdateStamp() {
return this.updateStamp;
}
private int indexOf(final String id) {
int low= 0;
int high= this.elementEntries.size() - 1;
while (low <= high) {
final int mid= (low + high) >>> 1;
final int diff= this.elementEntries.get(mid).id.compareTo(id);
if (diff < 0) {
low= mid + 1;
}
else if (diff > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -(low + 1);
}
private ElementEntry ensureElement(final String id) {
final int idx= indexOf(id);
if (idx >= 0) {
return this.elementEntries.get(idx);
}
else {
final ElementEntry entry= new ElementEntry(id);
this.elementEntries.add(-(idx + 1), entry);
return entry;
}
}
/** Adds or updates current positions */
private void addTracepoints(final ElementTracepoints tracepoints, final long updateStamp) {
synchronized (this.elementEntries) {
this.updateStamp= updateStamp;
this.srcfile= tracepoints.getSrcfile();
final ElementEntry elementEntry= ensureElement(
(tracepoints.getElementId().startsWith(TOPLEVEL_ELEMENT_ID)) ?
TOPLEVEL_ELEMENT_ID : tracepoints.getElementId() );
elementEntry.setTracepoints(tracepoints, updateStamp);
}
}
public @Nullable ElementEntry getElementEntry(final String id) {
synchronized (this.elementEntries) {
final int idx= indexOf(id);
return (idx >= 0) ? this.elementEntries.get(idx) : null;
}
}
public @Nullable ElementTracepoints findElementTracepoints(
final @Nullable String id, final long timestamp) {
if (id == null) {
return null;
}
final ElementEntry elementEntry= getElementEntry(id);
return (elementEntry != null) ?
elementEntry.findTracepoints(timestamp, this.states) :
null;
}
/**
* Returns list with single CurrentPositionCollection for current tracepoint positions.
* @param timestamp timestamp of srcfile or <code>0</code>
* @param srcref srcref of element to match or <code>null</code>
* @return list with CurrentPositionCollection or <code>null</code>
*/
public @Nullable ElementTracepointsCollection findCurrentElementTracepoints(
final @Nullable String id, final long timestamp, final int @Nullable [] srcref) {
if (id == null && srcref == null) {
return null;
}
ElementEntry elementEntry= null;
boolean isNestedMatch= false;
if (id != null) { // by id
elementEntry= getElementEntry(id);
}
else if (timestamp != 0) { // by timestamp
synchronized (this.elementEntries) {
for (final ElementEntry entry : this.elementEntries) {
if (entry.getId() == TOPLEVEL_ELEMENT_ID) {
continue;
}
final ElementTracepoints cand= entry.getTracepoints(timestamp);
if (cand != null && cand.getElementSrcref() != null
&& Srcref.contains(cand.getElementSrcref(), srcref) ) {
if (Srcref.equalsStart(cand.getElementSrcref(), srcref)) {
elementEntry= entry;
isNestedMatch= false;
break;
}
else {
elementEntry= entry;
isNestedMatch= true;
}
}
}
}
}
else {
synchronized (this.elementEntries) {
ElementTracepoints best= null;
boolean requireDim= false;
for (final ElementEntry entry : this.elementEntries) {
if (entry.getId() == TOPLEVEL_ELEMENT_ID) {
continue;
}
final ElementTracepoints cand= entry.getCurrentTracepoints();
if (cand != null && cand.getElementSrcref() != null
&& Srcref.equalsStart(cand.getElementSrcref(), srcref)
&& !isAllDeleted(cand.getPositions(), this.states) ) {
if (Srcref.equalsDim(cand.getElementSrcref(), srcref)) {
if (!requireDim) {
requireDim= true;
best= null;
}
}
else {
if (requireDim) {
continue;
}
}
if (best == null || entry.getUpdateStamp() > elementEntry.getUpdateStamp()) {
elementEntry= entry;
best= cand;
}
}
}
if (requireDim && best != null) {
return new ElementTracepointsCollection(elementEntry.getUpdateStamp(),
best, best, timestamp );
}
}
}
return (elementEntry != null) ?
(isNestedMatch) ?
elementEntry.findCurrentNestedTracepoints(timestamp, srcref, this.states) :
elementEntry.findCurrentTracepoints(timestamp, srcref, this.states) :
null;
}
/** Returns the list with current states. Synchronization required! */
public StateSet getStates() {
return this.states;
}
public @Nullable StateEntry getBreakpointState(final long id) {
synchronized (this.states) {
return AbstractTracepointManager.getBreakpointState(this.states, id);
}
}
public void addInstalled(final ElementTracepointsCollection collection) {
this.updatingInstalled.add(collection);
}
private void finishUpdatingInst(final RhEnv env, final boolean clear,
final StateSet installed, final StateSet changed) {
synchronized (this.states) {
for (int i= 0; i < this.states.size(); i++) {
final StateEntry stateEntry= this.states.get(i);
int isInstalled= 0;
boolean isUpdated= false;
for (int j= 0; j < this.updatingInstalled.size(); j++) {
final ElementTracepointsCollection collection= this.updatingInstalled.get(j);
if (indexOfTracepointById(collection.getCurrent().getPositions(), stateEntry.getId()) >= 0) {
isInstalled++;
isUpdated|= stateEntry.addUpdatingInstElement(collection);
}
}
if (isInstalled > 0) {
if (!stateEntry.instEnvs.contains(env) && stateEntry.instEnvs.add(env)) {
changed.add(stateEntry);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"[TP] Updating element tracepoint {0}: installed in {2} element(s) of {1} now.",
new Object[] {
stateEntry,
env,
(clear) ? isInstalled : "one or more" } );
}
}
else if (isUpdated) {
changed.add(stateEntry);
}
installed.add(stateEntry);
}
else if (clear) {
if (stateEntry.instEnvs.remove(env)) {
if (stateEntry.instEnvs.isEmpty()) {
changed.add(stateEntry);
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"[TP] Updating element tracepoint {0}: installed in NO element of {1} now.",
new Object[] {
stateEntry,
env } );
}
}
}
}
}
this.updatingInstalled.clear();
}
}
protected static class ElementEntry {
private final String id;
private final List<ElementTracepoints> history= new ArrayList<>();
private long updateStamp;
public ElementEntry(final String id) {
this.id= id;
}
public final String getId() {
return this.id;
}
/** Sets current element tracepoints */
protected void setTracepoints(final ElementTracepoints tracepoints, final long updateStamp) {
this.updateStamp= updateStamp;
if (this.history.isEmpty()) {
this.history.add(tracepoints);
}
else if (this.history.get(0).getSrcfile().getTimestamp() == tracepoints.getSrcfile().getTimestamp()) {
this.history.set(0, tracepoints);
}
else {
if (this.history.size() >= MAX_HISTORY) {
this.history.remove(this.history.size() - 1);
}
this.history.add(0, tracepoints);
}
}
public long getUpdateStamp() {
return this.updateStamp;
}
/** Returns current element tracepoints */
public @Nullable ElementTracepoints getCurrentTracepoints() {
synchronized (this.history) {
return (!this.history.isEmpty()) ? this.history.get(0) : null;
}
}
/** Returns positions for specified timestamp */
public @Nullable ElementTracepoints getTracepoints(final long timestamp) {
synchronized (this.history) {
if (!this.history.isEmpty()) {
if (timestamp != 0) {
for (int i= 0; i < this.history.size(); i++) {
final ElementTracepoints cand= this.history.get(i);
if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
return cand;
}
}
}
}
return null;
}
}
public @Nullable ElementTracepoints findTracepoints(
final long timestamp,
final @Nullable StateSet states) {
synchronized (this.history) {
if (!this.history.isEmpty()) {
final ElementTracepoints current= this.history.get(0);
int end= this.history.size();
if (states != null && !current.getPositions().isEmpty()
&& isAllDeleted(current.getPositions(), states) ) {
return NO_POSITION_ELEMENT_TRACEPOINTS;
}
if (end > 1 && !current.getPositions().isEmpty()) {
ElementTracepoints best= null;
List<TracepointPosition> timestampPositions= null;
if (timestamp != 0) {
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
if (containsPositionsById(cand.getPositions(), current.getPositions())) {
return cand;
}
best= cand;
timestampPositions= filterPositionsById(cand.getPositions(), current.getPositions());
end= i;
break;
}
}
if (timestampPositions != null && !timestampPositions.isEmpty() ) {
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (equalsPositions(cand.getPositions(), timestampPositions,
timestampPositions )) {
return cand;
}
}
}
}
if (best != null) {
return best;
}
}
return current;
}
return null;
}
}
/** Returns CurrentPositionCollection with current tracepoints positions */
public @Nullable ElementTracepointsCollection findCurrentTracepoints(
final long timestamp, final int @Nullable [] srcref,
final @Nullable StateSet states) {
synchronized (this.history) {
if (!this.history.isEmpty()) {
final ElementTracepoints current= this.history.get(0);
int end= this.history.size();
if (states != null && !current.getPositions().isEmpty()
&& isAllDeleted(current.getPositions(), states) ) {
return new ElementTracepointsCollection(this.updateStamp,
NO_POSITION_ELEMENT_TRACEPOINTS, NO_POSITION_ELEMENT_TRACEPOINTS, 0 );
}
if (end > 1 && !current.getPositions().isEmpty()) {
if (timestamp != 0) {
List<TracepointPosition> timestampActualPositions= null;
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
if (containsPositionsById(cand.getPositions(), current.getPositions())) {
return new ElementTracepointsCollection(this.updateStamp,
current, cand, timestamp );
}
timestampActualPositions= filterPositionsById(cand.getPositions(), current.getPositions());
end= i;
break;
}
}
if (timestampActualPositions != null && !timestampActualPositions.isEmpty()) {
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (containsPositionsById(cand.getPositions(), current.getPositions())
&& equalsPositions(cand.getPositions(), timestampActualPositions,
timestampActualPositions )) {
return new ElementTracepointsCollection(this.updateStamp,
current, cand, timestamp );
}
}
}
}
if (srcref != null) {
final List<ElementTracepoints> selected= new ArrayList<>(MAX_FUZZY);
selected.add(current);
for (int i= 1; i < end && selected.size() < MAX_FUZZY; i++) {
final ElementTracepoints cand= this.history.get(i);
if (cand.getElementSrcref() != null
&& Srcref.equalsDim(cand.getElementSrcref(), srcref)
&& containsPositionsById(cand.getPositions(), current.getPositions())
&& !equalsPositions(cand.getPositions(),
selected.get(selected.size() - 1).getPositions(),
current.getPositions() )) {
selected.add(cand);
}
}
return new ElementTracepointsCollection(this.updateStamp,
current, selected, timestamp );
}
}
return new ElementTracepointsCollection(this.updateStamp,
current, current, timestamp );
}
return null;
}
}
public @Nullable ElementTracepointsCollection findCurrentNestedTracepoints(
final long timestamp, final int[] srcref,
final @Nullable StateSet states) {
synchronized (this.history) {
if (!this.history.isEmpty()) {
final ElementTracepoints current= this.history.get(0);
int end= this.history.size();
if (current.getPositions().isEmpty()
|| (states != null && isAllDeleted(current.getPositions(), states)) ) {
return createNoPositionCollection();
}
if (timestamp != 0) {
int[] timestampSrcref= null;
List<TracepointPosition> timestampPositions= null;
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (equalsTimestamp(cand.getSrcfile().getTimestamp(), timestamp)) {
if (cand.getElementSrcref() == null) {
break;
}
final int[] index= findIndexBySrcref(cand.getPositions(),
nonNullAssert(cand.getElementSrcref()), srcref );
if (index != null) {
timestampSrcref= nonNullAssert(getSrcrefForIndex(
cand.getPositions(), index ));
timestampPositions= rebasePositionsByIndex(
cand.getPositions(), index );
if (containsPositionsById(cand.getPositions(), current.getPositions())) {
return createNestedCollection(current,
createNested(cand, timestampSrcref, filterPositionsById(
timestampPositions, current.getPositions() )),
timestamp, states );
}
}
end= i;
break;
}
}
if (timestampPositions != null && !timestampPositions.isEmpty()) {
for (int i= 0; i < end; i++) {
final ElementTracepoints cand= this.history.get(i);
if (containsPositionsById(cand.getPositions(), current.getPositions())) {
final List<TracepointPosition> compareList= filterPositionsById(
cand.getPositions(), timestampPositions );
final int[] index= findIndexByReference(compareList, timestampPositions);
if (index != null) {
final int[] candSrcref= nonNullAssert(getSrcrefForIndex(
compareList, index ));
final List<TracepointPosition> candPositions= rebasePositionsByIndex(
cand.getPositions(), index );
if (equalsPositions(candPositions, timestampPositions,
timestampPositions )) {
return createNestedCollection(current,
createNested(cand, candSrcref, filterPositionsById(
candPositions, current.getPositions() )),
timestamp, states );
}
}
}
}
{ final List<TracepointPosition> positions= filterPositionsById(
timestampPositions, current.getPositions() );
if (positions.isEmpty()
|| (states != null && isAllDeleted(positions, states)) ) {
return createNoPositionCollection();
}
}
}
return createNoChangeCollection();
}
}
return null;
}
}
private ElementTracepointsCollection createNoPositionCollection() {
return new ElementTracepointsCollection(this.updateStamp,
NO_POSITION_ELEMENT_TRACEPOINTS, NO_POSITION_ELEMENT_TRACEPOINTS, 0 );
}
private ElementTracepointsCollection createNoChangeCollection() {
return new ElementTracepointsCollection(this.updateStamp,
NO_CHANGE_ELEMENT_TRACEPOINTS, NO_CHANGE_ELEMENT_TRACEPOINTS, 0 );
}
private ElementTracepoints createNested(final ElementTracepoints cand,
final int[] srcref, final List<TracepointPosition> nestedPositions) {
return new ElementTracepoints(cand.getSrcfile(), this.id, srcref, nestedPositions);
}
private ElementTracepointsCollection createNestedCollection(final ElementTracepoints current,
final ElementTracepoints nested, final long requestedTimestamp,
final @Nullable StateSet states) {
final List<TracepointPosition> currentPositions= filterPositionsById(
current.getPositions(), nested.getPositions());
if (currentPositions.isEmpty()
|| (states != null && isAllDeleted(currentPositions, states)) ) {
return createNoPositionCollection();
}
return new ElementTracepointsCollection(this.updateStamp,
new ElementTracepoints(current.getSrcfile(), this.id, null, currentPositions),
nested, requestedTimestamp );
}
}
protected class StateEntry implements Comparable<StateEntry> {
private final String path;
private final long id;
private final List<RhEnv> instEnvs= new ArrayList<>();
private @Nullable ElementTracepoints instElement;
private long instUpdatingStamp;
private @Nullable ElementTracepointsCollection updatingInstElement;
private @Nullable TracepointState state;
private @Nullable ParsedExpr parsedExpr;
public StateEntry(final String path, final long id) {
this.path= path;
this.id= id;
}
public final long getId() {
return this.id;
}
public final boolean isInstalled() {
return !this.instEnvs.isEmpty();
}
public synchronized void setState(final TracepointState state) {
this.state= state;
}
public synchronized final @Nullable TracepointState getState() {
return this.state;
}
public synchronized ParsedExpr getParsedExpr(final String source) {
ParsedExpr pe= this.parsedExpr;
if (pe != null) {
if (pe.getSource().equals(source)) {
return pe;
}
AbstractTracepointManager.this.objManager.addToDispose(pe);
this.parsedExpr= null;
}
pe= new ParsedExpr(source);
if (this.state != null) {
this.parsedExpr= pe;
}
return pe;
}
public synchronized void removed() {
this.state= null;
if (this.parsedExpr != null) {
AbstractTracepointManager.this.objManager.addToDispose(this.parsedExpr);
this.parsedExpr= null;
}
}
private @Nullable TracepointEvent checkInstalled(final @Nullable ElementTracepoints prevElement) {
final ElementTracepoints element= this.instElement;
if (element != null && element != prevElement
&& (prevElement == null
|| !element.getElementId().equals(prevElement.getElementId()) )) {
return new TracepointEvent(TracepointEvent.KIND_INSTALLED,
0, element.getSrcfile().getPath(), this.id,
element.getElementId(), null, 0 );
}
return null;
}
private @Nullable TracepointEvent checkUninstalled() {
final ElementTracepoints element= this.instElement;
if (element != null) {
this.instElement= null;
return new TracepointEvent(TracepointEvent.KIND_UNINSTALLED,
0, element.getSrcfile().getPath(), this.id,
element.getElementId(), null, 0 );
}
return null;
}
private boolean addUpdatingInstElement(final ElementTracepointsCollection collection) {
if (this.updatingInstElement == null
|| collection.getUpdatingStamp() > this.updatingInstElement.getUpdatingStamp()) {
this.updatingInstElement= collection;
return true;
}
return false;
}
private void finishUpdatingInst(final List<TracepointEvent> events) {
TracepointEvent event= null;
if (isInstalled()) {
final ElementTracepoints prevElement= this.instElement;
if (this.updatingInstElement != null
&& (getClearUpdatingInst()
|| this.instElement == null
|| this.updatingInstElement.getUpdatingStamp() > this.instUpdatingStamp )) {
this.instElement= this.updatingInstElement.getCurrent();
this.instUpdatingStamp= this.updatingInstElement.getUpdatingStamp();
event= checkInstalled(prevElement);
}
}
else {
event= checkUninstalled();
}
if (event != null) {
events.add(event);
}
this.updatingInstElement= null;
}
private void removeInst(final RhEnv env, final List<TracepointEvent> events) {
TracepointEvent event= null;
if (isInstalled() && this.instEnvs.remove(env)
&& this.instEnvs.isEmpty() ) {
event= checkUninstalled();
}
if (event != null) {
events.add(event);
}
}
@Override
public int compareTo(final StateEntry other) {
return (this.id < other.id) ? -1 : (this.id != other.id) ? 1 :
this.path.compareTo(other.path);
}
private int compareToId(final long otherId) {
return (this.id < otherId) ? -1 : (this.id != otherId) ? 1 : 0;
}
@Override
public final int hashCode() {
return Long.hashCode(this.id);
}
@Override
public final boolean equals(final @Nullable Object obj) {
return (this == obj
|| (obj instanceof StateEntry
&& this.id == ((StateEntry) obj).id
&& this.path.equals(((StateEntry) obj).path)) );
}
@Override
public String toString() {
final StringBuilder sb= new StringBuilder(16 + 6 + this.path.length());
Tracepoints.append(this.id, sb);
sb.append(" in '");
sb.append(this.path);
sb.append('\'');
return sb.toString();
}
}
protected final ObjectManager objManager;
private final DbgListener listener;
private final Map<String, PathEntry> pathMap= new HashMap<>(32);
private @Nullable ImList<PathEntry> pathList;
private final Object stateUpdate= new Object();
private volatile boolean isAnyEnabled;
private final IdentitySet<String> envTags= new CopyOnWriteIdentityListSet<>();
private boolean isBreakpointsEnabled= true;
private @Nullable RhEnv updatingEnv;
private boolean updatingEnvComplete;
private final StateSet updatingStates= new StateSet(32);
public AbstractTracepointManager(final ObjectManager objManager, final DbgListener listener) {
this.objManager= objManager;
this.listener= listener;
this.objManager.addEnvListener(new EnvListener() {
@Override
public void onEnvRemoved(final RhEnv env, final boolean finalized) {
if (finalized && env.isReg(ENV_INSTALLED_TAG)) {
updateInstOnRemoved(env);
}
}
});
}
protected boolean isAnyTracepointEnabled() {
return this.isAnyEnabled;
}
protected PathEntry ensurePathEntry(final String path) {
synchronized (this.pathMap) {
PathEntry entry= this.pathMap.get(path);
if (entry == null) {
entry= new PathEntry(path);
this.pathMap.put(path, entry);
this.pathList= null;
}
return entry;
}
}
protected ImList<PathEntry> getPathEntries() {
synchronized (this.pathMap) {
ImList<PathEntry> pathList= this.pathList;
if (pathList == null) {
pathList= ImCollections.toList(this.pathMap.values());
this.pathList= pathList;
}
return pathList;
}
}
protected @Nullable PathEntry getPathEntry(final String path) {
synchronized (this.pathMap) {
return this.pathMap.get(path);
}
}
protected @Nullable PathEntry findPathEntryByFileName(final String fileName) {
synchronized (this.pathMap) {
for (final PathEntry entry : this.pathMap.values()) {
final SrcfileData srcfile= entry.srcfile;
if (srcfile != null && fileName.equals(srcfile.getName())) {
return entry;
}
}
return null;
}
}
private void addTracepoints(final ElementTracepoints tracepoints, final long updateStamp) {
final SrcfileData srcfile= tracepoints.getSrcfile();
final String srcfilePath= srcfile.getPath();
if (srcfilePath != null) {
final PathEntry entry= ensurePathEntry(srcfilePath);
entry.addTracepoints(tracepoints, updateStamp);
}
}
protected void addTracepoints(final List<? extends ElementTracepoints> tracepointsList) {
final long updateStamp= System.nanoTime();
for (final ElementTracepoints tracepoints : tracepointsList) {
addTracepoints(tracepoints, updateStamp);
}
}
/**
* Updates the state of the specified tracepoints
*
* @param request the request with tracepoint state updates
* @param reset if all existing tracepoint state should be reset
* @return update result status
*/
public RjsStatus updateTracepointStates(final TracepointStatesUpdate request) {
synchronized (this.stateUpdate) {
final List<String> reset;
if (request.getReset()) {
synchronized (this.pathMap) {
reset= new ArrayList<>(this.pathMap.keySet());
}
}
else {
reset= null;
}
boolean isAnyEnabled= false;
final List<StateEntry> oldStateEntries= new ArrayList<>();
final List<TracepointState> list= request.getStates();
for (int i= 0; i < list.size(); ) {
final String path= list.get(i).getFilePath();
if (path == null) {
i++;
continue;
}
final PathEntry pathEntry= ensurePathEntry(path);
synchronized (pathEntry.states) {
if (reset != null && reset.remove(path) && !pathEntry.states.isEmpty()) {
oldStateEntries.addAll(pathEntry.states);
pathEntry.states.clear();
}
for (; i < list.size(); i++) {
final TracepointState state= list.get(i);
if (!path.equals(state.getFilePath())) {
break;
}
final int idx= pathEntry.states.indexOfId(state.getId());
StateEntry entry= null;
if (idx >= 0) {
entry= pathEntry.states.get(idx);
}
if (state.getType() != Tracepoint.TYPE_DELETED) {
if (entry == null && reset != null) {
final int oldIdx= indexOfStateEntry(oldStateEntries, state.getId());
if (oldIdx >= 0) {
entry= oldStateEntries.remove(oldIdx);
}
}
if (entry == null) {
entry= new StateEntry(path, state.getId());
}
entry.setState(state);
if (idx < 0) {
pathEntry.states.add(entry);
}
isAnyEnabled |= state.isEnabled();
}
else if (idx >= 0) {
pathEntry.states.remove(idx);
oldStateEntries.add(entry);
}
}
}
onRemoved(oldStateEntries);
oldStateEntries.clear();
if (reset != null) {
this.isAnyEnabled= isAnyEnabled;
}
}
if (reset != null) {
for (final String path : reset) {
final PathEntry entry= ensurePathEntry(path);
if (!entry.states.isEmpty()) {
synchronized (entry.states) {
onRemoved(entry.states);
entry.states.clear();
}
}
}
}
return RjsStatus.OK_STATUS;
}
}
private void onRemoved(final List<StateEntry> entries) {
for (final StateEntry entry : entries) {
entry.removed();
}
}
/**
* Sets global enablement of breakpoints.
*
* @param enabled
*/
public void setBreakpointsEnabled(final boolean enabled) {
this.isBreakpointsEnabled= enabled;
}
/**
* Returns global enablement of breakpoints.
*
* @return
*/
public final boolean isBreakpointsEnabled() {
return this.isBreakpointsEnabled;
}
/**
* Registers the environment for tracepoint management.
*
* @param key
* @param envHandle handle of the environment
* @param objectNames object names to update
*/
public void registerEnv(String key, final Handle envHandle,
final @Nullable List<String> objectNames) {
key= key.intern();
this.envTags.add(key);
final RhEnv env= this.objManager.registerEnv(envHandle, key);
updateInst(env, objectNames);
finishUpdatingInst();
}
/**
* Deletes registration of the environment for tracepoint management.
*
* @param key
* @param envHandle handle of the environment
*/
public void unregisterEnv(final String key, final Handle envHandle) {
this.objManager.unregisterEnv(envHandle, key.intern());
}
/**
* Deletes registration of the environment for tracepoint management.
*/
public void unregisterEnvs(final String key) {
this.objManager.unregisterEnvs(key.intern());
}
protected void addEnvWithParents(final List<RhEnv> envs, final RhEnv env) throws RjsException {
RhEnv env0= env;
while (env0 != null && !envs.contains(env0)) {
envs.add(env0);
env0= this.objManager.getParentEnv(env0);
}
}
public RjsStatus installTracepoints(final ElementTracepointInstallationRequest request)
throws RjsException {
final List<? extends ElementTracepoints> tracepointsList= request.getRequests();
addTracepoints(tracepointsList);
if (containsCommonElement(tracepointsList)) {
updateInst();
}
return RjsStatus.OK_STATUS;
}
public RjsStatus installTracepoints(final FlagTracepointInstallationRequest request)
throws RjsException {
final byte[] types= request.getTypes();
final int[] flags= request.getFlags();
for (int i= 0; i < types.length; i++) {
switch (types[i]) {
case Tracepoint.TYPE_EB:
performUpdatingInstEB(flags[i]);
break;
default:
break;
}
}
return RjsStatus.OK_STATUS;
}
public void updateInst() throws RjsException {
try {
this.objManager.update();
final List<RhEnv> envs= new ArrayList<>(64);
envs.addAll(this.objManager.getSearchPath());
for (final RhEnv env : this.objManager.getStackFrames()) {
addEnvWithParents(envs, env);
}
for (final RhEnv env: this.objManager.getEnvs()) {
if ((env.isReg(ENV_INSTALLED_TAG) || env.isRegAny(this.envTags))
&& !envs.contains(env) ) {
envs.add(env);
}
}
for (final RhEnv env : envs) {
performUpdatingInst(env, null);
}
}
finally {
finishUpdatingInst();
}
}
public void updateInst(final RhEnv env, final @Nullable List<String> objectNames) {
try {
performUpdatingInst(env, objectNames);
}
finally {
finishUpdatingInst();
}
}
protected abstract void performUpdatingInstEB(final int i);
protected abstract void performUpdatingInst(final RhEnv env, final @Nullable List<String> objectNames);
protected void notifyListeners(final List<TracepointEvent> events) {
if (!events.isEmpty()) {
this.listener.handle(events);
}
}
protected void notifyListeners(final TracepointEvent event) {
this.listener.handle(ImCollections.newList(event));
}
protected void beginUpdatingInstEnv(final RhEnv env, final @Nullable List<String> namesToUpdate) {
this.updatingEnv= env;
this.updatingEnvComplete= (namesToUpdate == null);
if (LOGGER.isLoggable(Level.FINE)) {
if (namesToUpdate != null) {
LOGGER.log(Level.FINE,
"[TP] Updating element tracpoints in selected element(s) of {0}..." +
"\n\telementNames= {1}",
new Object[] { env, namesToUpdate } ); // TODO encode names
}
else {
LOGGER.log(Level.FINE,
"[TP] Updating element tracpoints in all elements of {0}...",
new Object[] { env } );
}
}
}
protected final boolean getClearUpdatingInst() {
return this.updatingEnvComplete;
}
protected void finishUpdatingInstEnv() {
final RhEnv env= this.updatingEnv;
if (env != null) {
try {
final boolean clear= this.updatingEnvComplete;
final StateSet prevInstalled= (StateSet) env.getData(ENV_INSTALLED_TAG);
final StateSet installed= (clear || prevInstalled == null) ?
new StateSet(0) : prevInstalled;
final List<PathEntry> pathEntries= getPathEntries();
for (final PathEntry entry : pathEntries) {
entry.finishUpdatingInst(env, clear, installed, this.updatingStates);
}
if (!installed.isEmpty()) {
if (installed != prevInstalled) {
env.addReg(ENV_INSTALLED_TAG, installed);
}
}
else {
if (prevInstalled != null) {
env.removeReg(ENV_INSTALLED_TAG);
}
}
}
catch (final Exception e) {
final LogRecord record= new LogRecord(Level.SEVERE,
"[TP] An error occured when updating tracepoint installation states for {0}." );
record.setParameters(new Object[] { env });
record.setThrown(e);
LOGGER.log(record);
}
finally {
this.updatingEnv= null;
}
}
}
protected void finishUpdatingInst() {
if (!this.updatingStates.isEmpty()) {
final List<TracepointEvent> events= new ArrayList<>();
for (final StateEntry entry : this.updatingStates) {
entry.finishUpdatingInst(events);
}
notifyListeners(events);
this.updatingStates.clear();
}
}
protected void updateInstOnRemoved(final RhEnv env) {
final StateSet installed= ObjectUtils.nonNullAssert(
(StateSet) env.getData(ENV_INSTALLED_TAG) );
final List<TracepointEvent> events= new ArrayList<>();
for (final StateEntry stateEntry : installed) {
stateEntry.removeInst(env, events);
}
notifyListeners(events);
}
}