InjectorDiagnostics.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.di.impl;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.apache.maven.di.Key;
import org.apache.maven.di.Scope;
/** Captures and serializes diagnostic output for one injector. */
final class InjectorDiagnostics {
// Duplicated from o.a.m.api.Constants.MAVEN_DI_DEBUG ��� maven-di cannot depend on maven-api-core
private static final String DEBUG_PROPERTY = "maven.di.debug";
private static final long NO_CAPTURED_REVISION = -1;
private static final String AMBIGUOUS = "ambiguous";
private final InjectorImpl injector;
private final Map<Key<?>, Set<Binding<?>>> bindings;
private final Map<Class<? extends Annotation>, Supplier<Scope>> scopes;
// Registration, as with the binding map itself, must not race with resolution.
private volatile long registryRevision;
private final Object diagnosticLock = new Object();
private volatile DiagnosticState diagnosticState;
private Deque<DiagnosticWork> diagnosticQueue;
private Thread diagnosticWriter;
InjectorDiagnostics(
InjectorImpl injector,
Map<Key<?>, Set<Binding<?>>> bindings,
Map<Class<? extends Annotation>, Supplier<Scope>> scopes) {
this.injector = injector;
this.bindings = bindings;
this.scopes = scopes;
}
void registryChanged() {
registryRevision++;
}
void reset() {
synchronized (diagnosticLock) {
diagnosticState = null;
}
}
String dumpBindings() {
return BindingSnapshot.capture(bindings, scopes, new DiagnosticIdentity(), false, registryRevision)
.render(injector, "explicit", false, null, injector.getBindingStyler());
}
void checkpoint(String reason, boolean partial) {
DiagnosticMode mode = diagnosticMode();
if (mode == DiagnosticMode.OFF) {
if (diagnosticState != null) {
reset();
}
return;
}
PrintStream output = System.out;
DiagnosticState state = diagnosticState;
if (!partial
&& state != null
&& state.mode == mode
&& state.capturedRevision == registryRevision
&& state.output.get() == output) {
return;
}
writeBindings(output, reason, partial, mode);
}
private static DiagnosticMode diagnosticMode() {
try {
String property = System.getProperty(DEBUG_PROPERTY);
if (property == null) {
return DiagnosticMode.OFF;
}
if (property.isEmpty() || Boolean.parseBoolean(property)) {
return DiagnosticMode.ALL;
}
return AMBIGUOUS.equalsIgnoreCase(property) ? DiagnosticMode.AMBIGUOUS : DiagnosticMode.OFF;
} catch (RuntimeException e) {
return DiagnosticMode.OFF;
}
}
private void writeBindings(PrintStream output, String reason, boolean partial, DiagnosticMode mode) {
DiagnosticWork work;
synchronized (diagnosticLock) {
DiagnosticState state = diagnosticState;
if (state == null || state.mode != mode || state.output.get() != output) {
state = new DiagnosticState(mode, output);
diagnosticState = state;
}
long revision = registryRevision;
if (!partial && state.capturedRevision == revision) {
return;
}
try {
BindingSnapshot snapshot = BindingSnapshot.capture(
bindings, scopes, state.identities, mode == DiagnosticMode.AMBIGUOUS, revision);
work = new DiagnosticWork(state, snapshot, output, reason, partial);
state.capturedRevision = revision;
if (diagnosticQueue == null) {
diagnosticQueue = new ArrayDeque<>();
}
diagnosticQueue.addLast(work);
} catch (RuntimeException e) {
// Diagnostics must not replace a registration or resolution failure. Retry on the next checkpoint.
return;
}
if (diagnosticWriter != null) {
// A callback on the writer cannot wait for itself; its captured state is drained after this block.
if (diagnosticWriter == Thread.currentThread()) {
return;
}
boolean interrupted = false;
while (!work.done) {
try {
diagnosticLock.wait();
} catch (InterruptedException e) {
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return;
}
diagnosticWriter = Thread.currentThread();
}
drainDiagnostics();
}
private void drainDiagnostics() {
try {
while (true) {
DiagnosticWork work;
BindingSnapshot previous;
synchronized (diagnosticLock) {
work = diagnosticQueue.pollFirst();
if (work == null) {
diagnosticWriter = null;
return;
}
if (diagnosticState != work.state) {
work.done = true;
diagnosticLock.notifyAll();
continue;
}
previous = work.state.emittedSnapshot;
}
try {
String dump = work.snapshot.render(
injector, work.reason, work.partial, previous, injector.getBindingStyler());
if (dump != null) {
injector.writeDump(work.output, dump);
synchronized (diagnosticLock) {
if (diagnosticState == work.state) {
work.state.emittedSnapshot = work.snapshot;
}
}
}
} catch (RuntimeException e) {
synchronized (diagnosticLock) {
work.state.emittedSnapshot = null;
work.state.capturedRevision = NO_CAPTURED_REVISION;
}
} finally {
synchronized (diagnosticLock) {
work.done = true;
diagnosticLock.notifyAll();
}
}
}
} finally {
synchronized (diagnosticLock) {
if (diagnosticWriter == Thread.currentThread()) {
diagnosticWriter = null;
diagnosticState = null;
diagnosticQueue.forEach(work -> work.done = true);
diagnosticQueue.clear();
diagnosticLock.notifyAll();
}
}
}
}
private enum DiagnosticMode {
OFF,
ALL,
AMBIGUOUS
}
private static final class DiagnosticWork {
final DiagnosticState state;
final BindingSnapshot snapshot;
final PrintStream output;
final String reason;
final boolean partial;
boolean done;
DiagnosticWork(
DiagnosticState state, BindingSnapshot snapshot, PrintStream output, String reason, boolean partial) {
this.state = state;
this.snapshot = snapshot;
this.output = output;
this.reason = reason;
this.partial = partial;
}
}
private static final class DiagnosticState {
final DiagnosticIdentity identities = new DiagnosticIdentity();
final DiagnosticMode mode;
volatile long capturedRevision = NO_CAPTURED_REVISION;
// A reused injector must not retain a previous embedded invocation's output stream.
final WeakReference<PrintStream> output;
BindingSnapshot emittedSnapshot;
DiagnosticState(DiagnosticMode mode, PrintStream output) {
this.mode = mode;
this.output = new WeakReference<>(output);
}
}
}