/**
* Copyright 2014 SAP AG
*
* 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 org.spotter.ext.detection.edc.utils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
public class MethodCallSetPerTid {
private static final long AVG_ENTRIES_PER_BUCKET = 10;
private final long tid;
private final HashMap<Long, Set<MethodCall>> buckets = new HashMap<>();
private final long min;
private final double bucketsize;
/**
* Constructor.
*
* @param min
* Minimum of used timestamps
* @param max
* Maximum of used timestamps
* @param numEntries
* Estimated number of calls inserted into this set
*/
public MethodCallSetPerTid(long min, long max, long numEntries, long tid) {
this.tid = tid;
this.min = min;
double avgEntriesPerTimeslot = ((double) numEntries) / ((double) (max - min));
double numberOfSlots = Math.max(1, (long) (AVG_ENTRIES_PER_BUCKET / avgEntriesPerTimeslot));
this.bucketsize = (max - min) / numberOfSlots;
}
/**
* Inserts a new MethodCall.
*
* @param call
* MethodCall to be inserted
*/
public void insert(MethodCall call) {
boolean isParent = false;
long startIdx = getStartIdx(call.getEnterTime());
long endIdx = getEndIdx(call.getExitTime());
for (long i = startIdx; i < endIdx; i++) {
Set<MethodCall> bucket = buckets.get(i);
Set<MethodCall> toRemove = new HashSet<>();
if (bucket == null) {
continue;
}
for (MethodCall existingCall : bucket) {
if (call.isParentOf(existingCall)) {
isParent = true;
call.addCall(existingCall);
toRemove.add(existingCall);
}
}
bucket.removeAll(toRemove);
}
boolean isChild = false;
if (!isParent) {
Set<MethodCall> firstBucket = buckets.get(startIdx);
if (firstBucket != null) {
for (MethodCall existingCall : firstBucket) {
if (existingCall.isParentOf(call)) {
isChild = true;
existingCall.addCall(call);
break;
}
}
}
}
if (isParent || !isChild) {
for (long i = startIdx; i < endIdx; i++) {
Set<MethodCall> bucket = buckets.get(i);
if (bucket == null) {
bucket = new HashSet<>();
buckets.put(i, bucket);
}
bucket.add(call);
}
}
}
/**
* Inserts a new MethodCall if it is a nested call of an existing call.
*
* @param call
* MethodCall to be inserted
* @return Iff the given call is a nested call
*/
public boolean insertIfNested(MethodCall call) {
boolean isChild = false;
Set<MethodCall> firstBucket = buckets.get(getStartIdx(call.getEnterTime()));
if (firstBucket != null) {
for (MethodCall existingCall : firstBucket) {
if (existingCall.isParentOf(call)) {
isChild = true;
existingCall.addCall(call);
break;
}
}
}
return isChild;
}
/**
* Removes a call.
*
* @param call
* MethodCall to be removed
*/
public void remove(MethodCall call) {
for (long i = getStartIdx(call.getEnterTime()); i < getEndIdx(call.getExitTime()); i++) {
Set<MethodCall> bucket = buckets.get(i);
if (bucket == null) {
continue;
}
boolean removed = bucket.remove(call);
if (!removed) {
for (MethodCall existingCall : bucket) {
existingCall.removeCall(call);
}
}
}
}
/**
* Removes all calls having the specified name.
*
* @param name
* Method name to be removed
*/
public void removeAllCallsWithName(String name) {
for (Entry<Long, Set<MethodCall>> bucketEntry : buckets.entrySet()) {
Set<MethodCall> toRemove = new HashSet<>();
for (MethodCall call : bucketEntry.getValue()) {
if (call.getOperation().equals(name)) {
toRemove.add(call);
} else {
call.removeNestedCallsWithName(name);
}
}
bucketEntry.getValue().removeAll(toRemove);
}
}
/**
* Returns all calls.
*
* @return All calls
*/
public Set<MethodCall> getAllCalls() {
Set<MethodCall> allCalls = new HashSet<>();
for (Entry<Long, Set<MethodCall>> bucketEntry : buckets.entrySet()) {
allCalls.addAll(bucketEntry.getValue());
}
return allCalls;
}
/**
* Returns the common thread id of the MethodCalls saved in this set.
*
* @return the common thread id of the MethodCalls saved in this set
*/
public long getThreadId() {
return tid;
}
private long getStartIdx(long timestamp) {
return (long) Math.floor((timestamp - min) / bucketsize);
}
private long getEndIdx(long timestamp) {
return (long) Math.ceil((timestamp - min) / bucketsize);
}
}