// 
// Decompiled by Procyon v0.6.0
// 

package io.sentry;

import org.jetbrains.annotations.TestOnly;
import io.sentry.util.thread.IThreadChecker;
import io.sentry.util.SpanUtils;
import java.util.Iterator;
import io.sentry.protocol.MeasurementValue;
import java.util.Map;
import io.sentry.protocol.SentryTransaction;
import java.util.concurrent.atomic.AtomicReference;
import java.util.ListIterator;
import io.sentry.util.CollectionUtils;
import io.sentry.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import io.sentry.protocol.Contexts;
import io.sentry.protocol.TransactionNameSource;
import java.util.concurrent.atomic.AtomicBoolean;
import io.sentry.util.AutoClosableReentrantLock;
import java.util.Timer;
import org.jetbrains.annotations.Nullable;
import java.util.TimerTask;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public final class SentryTracer implements ITransaction
{
    @NotNull
    private final SentryId eventId;
    @NotNull
    private final Span root;
    @NotNull
    private final List<Span> children;
    @NotNull
    private final IScopes scopes;
    @NotNull
    private String name;
    @NotNull
    private FinishStatus finishStatus;
    @Nullable
    private volatile TimerTask idleTimeoutTask;
    @Nullable
    private volatile TimerTask deadlineTimeoutTask;
    @Nullable
    private volatile Timer timer;
    @NotNull
    private final AutoClosableReentrantLock timerLock;
    @NotNull
    private final AutoClosableReentrantLock tracerLock;
    @NotNull
    private final AtomicBoolean isIdleFinishTimerRunning;
    @NotNull
    private final AtomicBoolean isDeadlineTimerRunning;
    @NotNull
    private TransactionNameSource transactionNameSource;
    @NotNull
    private final Instrumenter instrumenter;
    @NotNull
    private final Contexts contexts;
    @Nullable
    private final CompositePerformanceCollector compositePerformanceCollector;
    @NotNull
    private final TransactionOptions transactionOptions;
    
    public SentryTracer(@NotNull final TransactionContext context, @NotNull final IScopes scopes) {
        this(context, scopes, new TransactionOptions(), null);
    }
    
    public SentryTracer(@NotNull final TransactionContext context, @NotNull final IScopes scopes, @NotNull final TransactionOptions transactionOptions) {
        this(context, scopes, transactionOptions, null);
    }
    
    SentryTracer(@NotNull final TransactionContext context, @NotNull final IScopes scopes, @NotNull final TransactionOptions transactionOptions, @Nullable final CompositePerformanceCollector compositePerformanceCollector) {
        this.eventId = new SentryId();
        this.children = new CopyOnWriteArrayList<Span>();
        this.finishStatus = FinishStatus.NOT_FINISHED;
        this.timer = null;
        this.timerLock = new AutoClosableReentrantLock();
        this.tracerLock = new AutoClosableReentrantLock();
        this.isIdleFinishTimerRunning = new AtomicBoolean(false);
        this.isDeadlineTimerRunning = new AtomicBoolean(false);
        this.contexts = new Contexts();
        Objects.requireNonNull(context, "context is required");
        Objects.requireNonNull(scopes, "scopes are required");
        this.root = new Span(context, this, scopes, transactionOptions);
        this.name = context.getName();
        this.instrumenter = context.getInstrumenter();
        this.scopes = scopes;
        this.compositePerformanceCollector = (Boolean.TRUE.equals(this.isSampled()) ? compositePerformanceCollector : null);
        this.transactionNameSource = context.getTransactionNameSource();
        this.transactionOptions = transactionOptions;
        this.setDefaultSpanData(this.root);
        final SentryId continuousProfilerId = this.getProfilerId();
        if (!continuousProfilerId.equals(SentryId.EMPTY_ID) && Boolean.TRUE.equals(this.isSampled())) {
            this.contexts.setProfile(new ProfileContext(continuousProfilerId));
        }
        if (this.compositePerformanceCollector != null) {
            this.compositePerformanceCollector.start(this);
        }
        if (transactionOptions.getIdleTimeout() != null || transactionOptions.getDeadlineTimeout() != null) {
            this.timer = new Timer(true);
            this.scheduleDeadlineTimeout();
            this.scheduleFinish();
        }
    }
    
    @Override
    public void scheduleFinish() {
        try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
            if (this.timer != null) {
                final Long idleTimeout = this.transactionOptions.getIdleTimeout();
                if (idleTimeout != null) {
                    this.cancelIdleTimer();
                    this.isIdleFinishTimerRunning.set(true);
                    this.idleTimeoutTask = new TimerTask() {
                        @Override
                        public void run() {
                            SentryTracer.this.onIdleTimeoutReached();
                        }
                    };
                    try {
                        this.timer.schedule(this.idleTimeoutTask, idleTimeout);
                    }
                    catch (final Throwable e) {
                        this.scopes.getOptions().getLogger().log(SentryLevel.WARNING, "Failed to schedule finish timer", e);
                        this.onIdleTimeoutReached();
                    }
                }
            }
        }
    }
    
    private void onIdleTimeoutReached() {
        final SpanStatus status = this.getStatus();
        this.finish((status != null) ? status : SpanStatus.OK);
        this.isIdleFinishTimerRunning.set(false);
    }
    
    private void onDeadlineTimeoutReached() {
        final SpanStatus status = this.getStatus();
        this.forceFinish((status != null) ? status : SpanStatus.DEADLINE_EXCEEDED, this.transactionOptions.getIdleTimeout() != null, null);
        this.isDeadlineTimerRunning.set(false);
    }
    
    @NotNull
    @Override
    public void forceFinish(@NotNull final SpanStatus status, final boolean dropIfNoChildren, @Nullable final Hint hint) {
        if (this.isFinished()) {
            return;
        }
        final SentryDate finishTimestamp = this.scopes.getOptions().getDateProvider().now();
        final ListIterator<Span> iterator = CollectionUtils.reverseListIterator((CopyOnWriteArrayList<Span>)(CopyOnWriteArrayList)this.children);
        while (iterator.hasPrevious()) {
            final Span span = iterator.previous();
            span.setSpanFinishedCallback(null);
            span.finish(status, finishTimestamp);
        }
        this.finish(status, finishTimestamp, dropIfNoChildren, hint);
    }
    
    @Override
    public void finish(@Nullable final SpanStatus status, @Nullable final SentryDate finishDate, final boolean dropIfNoChildren, @Nullable final Hint hint) {
        SentryDate finishTimestamp = this.root.getFinishDate();
        if (finishDate != null) {
            finishTimestamp = finishDate;
        }
        if (finishTimestamp == null) {
            finishTimestamp = this.scopes.getOptions().getDateProvider().now();
        }
        final Iterator<Span> iterator = this.children.iterator();
        Span span = null;
        while (iterator.hasNext()) {
            span = iterator.next();
            if (span.getOptions().isIdle()) {
                span.finish((status != null) ? status : this.getSpanContext().status, finishTimestamp);
            }
        }
        this.finishStatus = FinishStatus.finishing(status);
        if (!this.root.isFinished() && (!this.transactionOptions.isWaitForChildren() || this.hasAllChildrenFinished())) {
            final AtomicReference<List<PerformanceCollectionData>> performanceCollectionData = new AtomicReference<List<PerformanceCollectionData>>();
            final SpanFinishedCallback oldCallback = this.root.getSpanFinishedCallback();
            this.root.setSpanFinishedCallback(span -> {
                if (oldCallback != null) {
                    oldCallback.execute(span);
                }
                final TransactionFinishedCallback finishedCallback = this.transactionOptions.getTransactionFinishedCallback();
                if (finishedCallback != null) {
                    finishedCallback.execute(this);
                }
                if (this.compositePerformanceCollector != null) {
                    performanceCollectionData.set(this.compositePerformanceCollector.stop(this));
                }
                return;
            });
            this.root.finish(this.finishStatus.spanStatus, finishTimestamp);
            ProfilingTraceData profilingTraceData = null;
            if (Boolean.TRUE.equals(this.isSampled()) && Boolean.TRUE.equals(this.isProfileSampled())) {
                profilingTraceData = this.scopes.getOptions().getTransactionProfiler().onTransactionFinish(this, performanceCollectionData.get(), this.scopes.getOptions());
            }
            if (this.scopes.getOptions().isContinuousProfilingEnabled() && this.scopes.getOptions().getProfileLifecycle() == ProfileLifecycle.TRACE && this.root.getSpanContext().getProfilerId().equals(SentryId.EMPTY_ID)) {
                this.scopes.getOptions().getContinuousProfiler().stopProfiler(ProfileLifecycle.TRACE);
            }
            if (performanceCollectionData.get() != null) {
                performanceCollectionData.get().clear();
            }
            final SentryTransaction transaction;
            this.scopes.configureScope(scope -> scope.withTransaction(transaction -> {
                if (transaction == this) {
                    scope.clearTransaction();
                }
            }));
            transaction = new SentryTransaction(this);
            if (this.timer != null) {
                try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
                    if (this.timer != null) {
                        this.cancelIdleTimer();
                        this.cancelDeadlineTimer();
                        this.timer.cancel();
                        this.timer = null;
                    }
                }
            }
            if (dropIfNoChildren && this.children.isEmpty() && this.transactionOptions.getIdleTimeout() != null) {
                this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "Dropping idle transaction %s because it has no child spans", this.name);
                return;
            }
            transaction.getMeasurements().putAll(this.root.getMeasurements());
            this.scopes.captureTransaction(transaction, this.traceContext(), hint, profilingTraceData);
        }
    }
    
    private void cancelIdleTimer() {
        try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
            if (this.idleTimeoutTask != null) {
                this.idleTimeoutTask.cancel();
                this.isIdleFinishTimerRunning.set(false);
                this.idleTimeoutTask = null;
            }
        }
    }
    
    private void scheduleDeadlineTimeout() {
        final Long deadlineTimeOut = this.transactionOptions.getDeadlineTimeout();
        if (deadlineTimeOut != null) {
            try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
                if (this.timer != null) {
                    this.cancelDeadlineTimer();
                    this.isDeadlineTimerRunning.set(true);
                    this.deadlineTimeoutTask = new TimerTask() {
                        @Override
                        public void run() {
                            SentryTracer.this.onDeadlineTimeoutReached();
                        }
                    };
                    try {
                        this.timer.schedule(this.deadlineTimeoutTask, deadlineTimeOut);
                    }
                    catch (final Throwable e) {
                        this.scopes.getOptions().getLogger().log(SentryLevel.WARNING, "Failed to schedule finish timer", e);
                        this.onDeadlineTimeoutReached();
                    }
                }
            }
        }
    }
    
    private void cancelDeadlineTimer() {
        try (final ISentryLifecycleToken ignored = this.timerLock.acquire()) {
            if (this.deadlineTimeoutTask != null) {
                this.deadlineTimeoutTask.cancel();
                this.isDeadlineTimerRunning.set(false);
                this.deadlineTimeoutTask = null;
            }
        }
    }
    
    @NotNull
    public List<Span> getChildren() {
        return this.children;
    }
    
    @NotNull
    @Override
    public SentryDate getStartDate() {
        return this.root.getStartDate();
    }
    
    @Nullable
    @Override
    public SentryDate getFinishDate() {
        return this.root.getFinishDate();
    }
    
    @NotNull
    ISpan startChild(@NotNull final SpanId parentSpanId, @NotNull final String operation, @Nullable final String description) {
        return this.startChild(parentSpanId, operation, description, new SpanOptions());
    }
    
    @NotNull
    ISpan startChild(@NotNull final SpanId parentSpanId, @NotNull final String operation, @Nullable final String description, @NotNull final SpanOptions spanOptions) {
        return this.createChild(parentSpanId, operation, description, spanOptions);
    }
    
    @NotNull
    ISpan startChild(@NotNull final SpanId parentSpanId, @NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp, @NotNull final Instrumenter instrumenter) {
        final SpanContext spanContext = this.getSpanContext().copyForChild(operation, parentSpanId, null);
        spanContext.setDescription(description);
        spanContext.setInstrumenter(instrumenter);
        final SpanOptions spanOptions = new SpanOptions();
        spanOptions.setStartTimestamp(timestamp);
        return this.createChild(spanContext, spanOptions);
    }
    
    @NotNull
    ISpan startChild(@NotNull final SpanId parentSpanId, @NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp, @NotNull final Instrumenter instrumenter, @NotNull final SpanOptions spanOptions) {
        final SpanContext spanContext = this.getSpanContext().copyForChild(operation, parentSpanId, null);
        spanContext.setDescription(description);
        spanContext.setInstrumenter(instrumenter);
        spanOptions.setStartTimestamp(timestamp);
        return this.createChild(spanContext, spanOptions);
    }
    
    @NotNull
    private ISpan createChild(@NotNull final SpanId parentSpanId, @NotNull final String operation, @Nullable final String description, @NotNull final SpanOptions options) {
        final SpanContext spanContext = this.getSpanContext().copyForChild(operation, parentSpanId, null);
        spanContext.setDescription(description);
        spanContext.setInstrumenter(Instrumenter.SENTRY);
        return this.createChild(spanContext, options);
    }
    
    @NotNull
    private ISpan createChild(@NotNull final SpanContext spanContext, @NotNull final SpanOptions spanOptions) {
        if (this.root.isFinished()) {
            return NoOpSpan.getInstance();
        }
        if (!this.instrumenter.equals(spanContext.getInstrumenter())) {
            return NoOpSpan.getInstance();
        }
        if (SpanUtils.isIgnored(this.scopes.getOptions().getIgnoredSpanOrigins(), spanOptions.getOrigin())) {
            return NoOpSpan.getInstance();
        }
        final SpanId parentSpanId = spanContext.getParentSpanId();
        final String operation = spanContext.getOperation();
        final String description = spanContext.getDescription();
        if (this.children.size() < this.scopes.getOptions().getMaxSpans()) {
            Objects.requireNonNull(parentSpanId, "parentSpanId is required");
            Objects.requireNonNull(operation, "operation is required");
            this.cancelIdleTimer();
            final Span span = new Span(this, this.scopes, spanContext, spanOptions, finishingSpan -> {
                if (this.compositePerformanceCollector != null) {
                    this.compositePerformanceCollector.onSpanFinished(finishingSpan);
                }
                final FinishStatus finishStatus = this.finishStatus;
                if (this.transactionOptions.getIdleTimeout() != null) {
                    if (!this.transactionOptions.isWaitForChildren() || this.hasAllChildrenFinished()) {
                        this.scheduleFinish();
                    }
                }
                else if (finishStatus.isFinishing) {
                    this.finish(finishStatus.spanStatus);
                }
                return;
            });
            this.setDefaultSpanData(span);
            this.children.add(span);
            if (this.compositePerformanceCollector != null) {
                this.compositePerformanceCollector.onSpanStarted(span);
            }
            return span;
        }
        this.scopes.getOptions().getLogger().log(SentryLevel.WARNING, "Span operation: %s, description: %s dropped due to limit reached. Returning NoOpSpan.", operation, description);
        return NoOpSpan.getInstance();
    }
    
    private void setDefaultSpanData(@NotNull final ISpan span) {
        final IThreadChecker threadChecker = this.scopes.getOptions().getThreadChecker();
        final SentryId profilerId = this.getProfilerId();
        if (!profilerId.equals(SentryId.EMPTY_ID) && Boolean.TRUE.equals(span.isSampled())) {
            span.setData("profiler_id", profilerId.toString());
        }
        span.setData("thread.id", String.valueOf(threadChecker.currentThreadSystemId()));
        span.setData("thread.name", threadChecker.getCurrentThreadName());
    }
    
    @NotNull
    private SentryId getProfilerId() {
        return this.root.getSpanContext().getProfilerId().equals(SentryId.EMPTY_ID) ? this.scopes.getOptions().getContinuousProfiler().getProfilerId() : this.root.getSpanContext().getProfilerId();
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation) {
        return this.startChild(operation, null);
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp, @NotNull final Instrumenter instrumenter) {
        return this.startChild(operation, description, timestamp, instrumenter, new SpanOptions());
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp, @NotNull final Instrumenter instrumenter, @NotNull final SpanOptions spanOptions) {
        return this.createChild(operation, description, timestamp, instrumenter, spanOptions);
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp) {
        return this.createChild(operation, description, timestamp, Instrumenter.SENTRY, new SpanOptions());
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation, @Nullable final String description) {
        return this.startChild(operation, description, null, Instrumenter.SENTRY, new SpanOptions());
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final String operation, @Nullable final String description, @NotNull final SpanOptions spanOptions) {
        return this.createChild(operation, description, null, Instrumenter.SENTRY, spanOptions);
    }
    
    @NotNull
    @Override
    public ISpan startChild(@NotNull final SpanContext spanContext, @NotNull final SpanOptions spanOptions) {
        return this.createChild(spanContext, spanOptions);
    }
    
    @NotNull
    private ISpan createChild(@NotNull final String operation, @Nullable final String description, @Nullable final SentryDate timestamp, @NotNull final Instrumenter instrumenter, @NotNull final SpanOptions spanOptions) {
        if (this.root.isFinished()) {
            return NoOpSpan.getInstance();
        }
        if (!this.instrumenter.equals(instrumenter)) {
            return NoOpSpan.getInstance();
        }
        if (this.children.size() < this.scopes.getOptions().getMaxSpans()) {
            return this.root.startChild(operation, description, timestamp, instrumenter, spanOptions);
        }
        this.scopes.getOptions().getLogger().log(SentryLevel.WARNING, "Span operation: %s, description: %s dropped due to limit reached. Returning NoOpSpan.", operation, description);
        return NoOpSpan.getInstance();
    }
    
    @NotNull
    @Override
    public SentryTraceHeader toSentryTrace() {
        return this.root.toSentryTrace();
    }
    
    @Override
    public void finish() {
        this.finish(this.getStatus());
    }
    
    @Override
    public void finish(@Nullable final SpanStatus status) {
        this.finish(status, null);
    }
    
    @ApiStatus.Internal
    @Override
    public void finish(@Nullable final SpanStatus status, @Nullable final SentryDate finishDate) {
        this.finish(status, finishDate, true, null);
    }
    
    @Nullable
    @Override
    public TraceContext traceContext() {
        if (this.scopes.getOptions().isTraceSampling()) {
            final Baggage baggage = this.getSpanContext().getBaggage();
            if (baggage != null) {
                this.updateBaggageValues(baggage);
                return baggage.toTraceContext();
            }
        }
        return null;
    }
    
    private void updateBaggageValues(@NotNull final Baggage baggage) {
        try (final ISentryLifecycleToken ignored = this.tracerLock.acquire()) {
            if (baggage.isMutable()) {
                final AtomicReference<SentryId> replayId = new AtomicReference<SentryId>();
                this.scopes.configureScope(scope -> replayId.set(scope.getReplayId()));
                baggage.setValuesFromTransaction(this.getSpanContext().getTraceId(), replayId.get(), this.scopes.getOptions(), this.getSamplingDecision(), this.getName(), this.getTransactionNameSource());
                baggage.freeze();
            }
        }
    }
    
    @Nullable
    @Override
    public BaggageHeader toBaggageHeader(@Nullable final List<String> thirdPartyBaggageHeaders) {
        if (this.scopes.getOptions().isTraceSampling()) {
            final Baggage baggage = this.getSpanContext().getBaggage();
            if (baggage != null) {
                this.updateBaggageValues(baggage);
                return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders);
            }
        }
        return null;
    }
    
    private boolean hasAllChildrenFinished() {
        final ListIterator<Span> iterator = this.children.listIterator();
        while (iterator.hasNext()) {
            final Span span = iterator.next();
            if (!span.isFinished() && span.getFinishDate() == null) {
                return false;
            }
        }
        return true;
    }
    
    @Override
    public void setOperation(@NotNull final String operation) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Operation %s cannot be set", operation);
            return;
        }
        this.root.setOperation(operation);
    }
    
    @NotNull
    @Override
    public String getOperation() {
        return this.root.getOperation();
    }
    
    @Override
    public void setDescription(@Nullable final String description) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Description %s cannot be set", description);
            return;
        }
        this.root.setDescription(description);
    }
    
    @Nullable
    @Override
    public String getDescription() {
        return this.root.getDescription();
    }
    
    @Override
    public void setStatus(@Nullable final SpanStatus status) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Status %s cannot be set", (status == null) ? "null" : status.name());
            return;
        }
        this.root.setStatus(status);
    }
    
    @Nullable
    @Override
    public SpanStatus getStatus() {
        return this.root.getStatus();
    }
    
    @Override
    public void setThrowable(@Nullable final Throwable throwable) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Throwable cannot be set", new Object[0]);
            return;
        }
        this.root.setThrowable(throwable);
    }
    
    @Nullable
    @Override
    public Throwable getThrowable() {
        return this.root.getThrowable();
    }
    
    @NotNull
    @Override
    public SpanContext getSpanContext() {
        return this.root.getSpanContext();
    }
    
    @Override
    public void setTag(@Nullable final String key, @Nullable final String value) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Tag %s cannot be set", key);
            return;
        }
        this.root.setTag(key, value);
    }
    
    @Nullable
    @Override
    public String getTag(@Nullable final String key) {
        return this.root.getTag(key);
    }
    
    @Override
    public boolean isFinished() {
        return this.root.isFinished();
    }
    
    @Override
    public void setData(@Nullable final String key, @Nullable final Object value) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Data %s cannot be set", key);
            return;
        }
        this.root.setData(key, value);
    }
    
    @Nullable
    @Override
    public Object getData(@Nullable final String key) {
        return this.root.getData(key);
    }
    
    @ApiStatus.Internal
    public void setMeasurementFromChild(@NotNull final String name, @NotNull final Number value) {
        if (!this.root.getMeasurements().containsKey(name)) {
            this.setMeasurement(name, value);
        }
    }
    
    @ApiStatus.Internal
    public void setMeasurementFromChild(@NotNull final String name, @NotNull final Number value, @NotNull final MeasurementUnit unit) {
        if (!this.root.getMeasurements().containsKey(name)) {
            this.setMeasurement(name, value, unit);
        }
    }
    
    @Override
    public void setMeasurement(@NotNull final String name, @NotNull final Number value) {
        this.root.setMeasurement(name, value);
    }
    
    @Override
    public void setMeasurement(@NotNull final String name, @NotNull final Number value, @NotNull final MeasurementUnit unit) {
        this.root.setMeasurement(name, value, unit);
    }
    
    @Nullable
    public Map<String, Object> getData() {
        return this.root.getData();
    }
    
    @Nullable
    @Override
    public Boolean isSampled() {
        return this.root.isSampled();
    }
    
    @Nullable
    @Override
    public Boolean isProfileSampled() {
        return this.root.isProfileSampled();
    }
    
    @Nullable
    @Override
    public TracesSamplingDecision getSamplingDecision() {
        return this.root.getSamplingDecision();
    }
    
    @Override
    public void setName(@NotNull final String name) {
        this.setName(name, TransactionNameSource.CUSTOM);
    }
    
    @ApiStatus.Internal
    @Override
    public void setName(@NotNull final String name, @NotNull final TransactionNameSource transactionNameSource) {
        if (this.root.isFinished()) {
            this.scopes.getOptions().getLogger().log(SentryLevel.DEBUG, "The transaction is already finished. Name %s cannot be set", name);
            return;
        }
        this.name = name;
        this.transactionNameSource = transactionNameSource;
    }
    
    @NotNull
    @Override
    public String getName() {
        return this.name;
    }
    
    @NotNull
    @Override
    public TransactionNameSource getTransactionNameSource() {
        return this.transactionNameSource;
    }
    
    @NotNull
    @Override
    public List<Span> getSpans() {
        return this.children;
    }
    
    @Nullable
    @Override
    public ISpan getLatestActiveSpan() {
        final ListIterator<Span> iterator = CollectionUtils.reverseListIterator((CopyOnWriteArrayList<Span>)(CopyOnWriteArrayList)this.children);
        while (iterator.hasPrevious()) {
            final Span span = iterator.previous();
            if (!span.isFinished()) {
                return span;
            }
        }
        return null;
    }
    
    @NotNull
    @Override
    public SentryId getEventId() {
        return this.eventId;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public ISentryLifecycleToken makeCurrent() {
        this.scopes.configureScope(scope -> scope.setTransaction(this));
        return NoOpScopesLifecycleToken.getInstance();
    }
    
    @NotNull
    Span getRoot() {
        return this.root;
    }
    
    @TestOnly
    @Nullable
    TimerTask getIdleTimeoutTask() {
        return this.idleTimeoutTask;
    }
    
    @TestOnly
    @Nullable
    TimerTask getDeadlineTimeoutTask() {
        return this.deadlineTimeoutTask;
    }
    
    @TestOnly
    @Nullable
    Timer getTimer() {
        return this.timer;
    }
    
    @TestOnly
    @NotNull
    AtomicBoolean isFinishTimerRunning() {
        return this.isIdleFinishTimerRunning;
    }
    
    @TestOnly
    @NotNull
    AtomicBoolean isDeadlineTimerRunning() {
        return this.isDeadlineTimerRunning;
    }
    
    @ApiStatus.Internal
    @Override
    public void setContext(@Nullable final String key, @Nullable final Object context) {
        this.contexts.put(key, context);
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public Contexts getContexts() {
        return this.contexts;
    }
    
    @Override
    public boolean updateEndDate(@NotNull final SentryDate date) {
        return this.root.updateEndDate(date);
    }
    
    @Override
    public boolean isNoOp() {
        return false;
    }
    
    @Override
    public void addFeatureFlag(@Nullable final String flag, @Nullable final Boolean result) {
        this.root.addFeatureFlag(flag, result);
    }
    
    private static final class FinishStatus
    {
        static final FinishStatus NOT_FINISHED;
        private final boolean isFinishing;
        @Nullable
        private final SpanStatus spanStatus;
        
        @NotNull
        static FinishStatus finishing(@Nullable final SpanStatus finishStatus) {
            return new FinishStatus(true, finishStatus);
        }
        
        @NotNull
        private static FinishStatus notFinished() {
            return new FinishStatus(false, null);
        }
        
        private FinishStatus(final boolean isFinishing, @Nullable final SpanStatus spanStatus) {
            this.isFinishing = isFinishing;
            this.spanStatus = spanStatus;
        }
        
        static {
            NOT_FINISHED = notFinished();
        }
    }
}
