• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcgay / maven-profiler / #284

20 Apr 2025 06:01PM UTC coverage: 86.512% (-0.4%) from 86.916%
#284

push

jcgay
Prevent NPE when extension is initiated after another one

When maven-profiler is installed in the Maven distribution ext folder
and a project uses other extension from the .mvn/extensions.xml configuration,
maven-profiler was failing with NPE in fr.jcgay.maven.profiler.ProfilerEventSpy#onEvent.

This is because the #init method of the extension was not yet called when Maven was loading
extension from .mvn/extensions.xml. Maven was firing event intercepted by maven-profiler
but as it was not yet initiated, the configuration and statistics internal state was not ready.

This commit also initiate its state in the constructor.
It will prevent NPE but the statistics gathered between the instantiation and the initialization
will be lost anyway 😥. Because #init will clean internal state, and we need this to have the extension
functional with mvnd.

Fixes #199

0 of 2 new or added lines in 1 file covered. (0.0%)

372 of 430 relevant lines covered (86.51%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.25
/src/main/java/fr/jcgay/maven/profiler/ProfilerEventSpy.java
1
package fr.jcgay.maven.profiler;
2

3
import com.google.common.annotations.VisibleForTesting;
4
import com.google.common.base.Joiner;
5
import com.google.common.base.Stopwatch;
6
import fr.jcgay.maven.profiler.reporting.ReportDirectory;
7
import fr.jcgay.maven.profiler.reporting.template.Data;
8
import fr.jcgay.maven.profiler.reporting.template.EntryAndTime;
9
import fr.jcgay.maven.profiler.reporting.template.Project;
10
import fr.jcgay.maven.profiler.sorting.Sorter;
11
import org.apache.maven.eventspy.AbstractEventSpy;
12
import org.apache.maven.eventspy.EventSpy;
13
import org.apache.maven.execution.DefaultMavenExecutionRequest;
14
import org.apache.maven.execution.ExecutionEvent;
15
import org.apache.maven.plugin.MojoExecution;
16
import org.apache.maven.project.MavenProject;
17
import org.codehaus.plexus.component.annotations.Component;
18
import org.codehaus.plexus.component.annotations.Requirement;
19
import org.codehaus.plexus.logging.Logger;
20
import org.codehaus.plexus.logging.console.ConsoleLogger;
21
import org.eclipse.aether.RepositoryEvent;
22
import org.eclipse.aether.artifact.Artifact;
23

24
import java.util.ArrayList;
25
import java.util.Date;
26
import java.util.LinkedHashSet;
27
import java.util.List;
28
import java.util.Map;
29
import java.util.Map.Entry;
30
import java.util.function.Supplier;
31

32
import static fr.jcgay.maven.profiler.KnownElapsedTimeTicker.aStopWatchWithElapsedTime;
33
import static java.util.concurrent.TimeUnit.MILLISECONDS;
34
import static org.apache.maven.execution.ExecutionEvent.Type.ProjectDiscoveryStarted;
35
import static org.apache.maven.execution.ExecutionEvent.Type.SessionStarted;
36
import static org.eclipse.aether.RepositoryEvent.EventType.ARTIFACT_DOWNLOADED;
37
import static org.eclipse.aether.RepositoryEvent.EventType.ARTIFACT_DOWNLOADING;
38

39
@Component(role = EventSpy.class, hint = "profiler", description = "Measure times taken by Maven.")
40
public class ProfilerEventSpy extends AbstractEventSpy {
41

42
    private final Supplier<Statistics> statisticsSupplier;
43
    private final Supplier<Configuration> configurationSupplier;
44
    private final Supplier<Date> now;
45

46
    private Configuration configuration;
47
    private Statistics statistics;
48

49
    @Requirement
50
    private Logger logger;
51

52
    public ProfilerEventSpy() {
×
53
        this.statisticsSupplier = Statistics::new;
×
NEW
54
        this.statistics = statisticsSupplier.get();
×
55
        this.configurationSupplier = Configuration::read;
×
NEW
56
        this.configuration = configurationSupplier.get();
×
57
        this.now = Date::new;
×
58
    }
×
59

60
    @VisibleForTesting
61
    ProfilerEventSpy(Supplier<Statistics> statisticsSupplier, Supplier<Configuration> configurationSupplier, Supplier<Date> now) {
1✔
62
        this.statisticsSupplier = statisticsSupplier;
1✔
63
        this.configurationSupplier = configurationSupplier;
1✔
64
        this.logger = new ConsoleLogger();
1✔
65
        this.now = now;
1✔
66
    }
1✔
67

68
    @Override
69
    public void init(Context context) throws Exception {
70
        super.init(context);
1✔
71

72
        this.configuration = configurationSupplier.get();
1✔
73
        this.statistics = statisticsSupplier.get();
1✔
74

75
        if (configuration.isProfiling()) {
1✔
76
            logger.info("Profiling mvn execution...");
1✔
77
        }
78
    }
1✔
79

80
    @Override
81
    public void onEvent(Object event) throws Exception {
82
        super.onEvent(event);
1✔
83
        if (configuration.isProfiling()) {
1✔
84
            if (event instanceof DefaultMavenExecutionRequest) {
1✔
85
                DefaultMavenExecutionRequest mavenEvent = (DefaultMavenExecutionRequest) event;
1✔
86
                statistics.setGoals(new LinkedHashSet<>(mavenEvent.getGoals()));
1✔
87
                if (configuration.shouldPrintParameters()) {
1✔
88
                    statistics.setProperties(mavenEvent.getUserProperties());
1✔
89
                }
90
            } else if (event instanceof ExecutionEvent) {
1✔
91
                storeExecutionEvent((ExecutionEvent) event);
1✔
92
                trySaveTopProject((ExecutionEvent) event);
1✔
93
                storeStartTime((ExecutionEvent) event);
1✔
94
            } else if (event instanceof RepositoryEvent) {
1✔
95
                storeDownloadingArtifacts((RepositoryEvent) event);
1✔
96
            }
97
        }
98
    }
1✔
99

100
    private void storeStartTime(ExecutionEvent event) {
101
        if (event.getType() == ProjectDiscoveryStarted) {
1✔
102
            statistics.setStartTime(event.getSession().getStartTime());
1✔
103
        }
104
    }
1✔
105

106
    @Override
107
    public void close() throws Exception {
108
        super.close();
1✔
109
        if (configuration.isProfiling()) {
1✔
110
            Date finishTime = now.get();
1✔
111
            Data context = new Data()
1✔
112
                .setProjects(sortedProjects())
1✔
113
                .setDate(finishTime)
1✔
114
                .setTopProjectName(statistics.topProject().getName())
1✔
115
                .setProfileName(configuration.profileName())
1✔
116
                .setGoals(Joiner.on(' ').join(statistics.goals()))
1✔
117
                .setParameters(statistics.properties());
1✔
118
            setDownloads(context);
1✔
119

120
            if (statistics.getStartTime() != null) {
1✔
121
                context.setBuildTime(aStopWatchWithElapsedTime(MILLISECONDS.toNanos(finishTime.getTime() - statistics.getStartTime().getTime())));
1✔
122
            }
123

124
            configuration.reporter().write(context, new ReportDirectory(statistics.topProject()));
1✔
125
        }
126
    }
1✔
127

128
    private void trySaveTopProject(ExecutionEvent event) {
129
        if (event.getType() == SessionStarted) {
1✔
130
            statistics.setTopProject(event.getSession().getTopLevelProject());
×
131
        }
132
    }
1✔
133

134
    private List<Project> sortedProjects() {
135
        Sorter sorter = configuration.sorter();
1✔
136

137
        List<Project> result = new ArrayList<>();
1✔
138
        Map<MavenProject, Stopwatch> allProjectsWithTimer = statistics.projects();
1✔
139
        for (MavenProject project : sorter.projects(allProjectsWithTimer)) {
1✔
140
            Project currentProject = new Project(project.getName(), allProjectsWithTimer.get(project));
1✔
141
            for (Entry<MojoExecution, Stopwatch> mojo : sorter.mojoExecutionsOf(project, statistics.executions())) {
1✔
142
                currentProject.addMojoTime(new EntryAndTime<>(mojo.getKey(), mojo.getValue()));
1✔
143
            }
1✔
144
            result.add(currentProject);
1✔
145
        }
1✔
146
        return result;
1✔
147
    }
148

149
    private void setDownloads(Data data) {
150
        Map<Artifact, Stopwatch> downloadedArtifacts = statistics.downloads();
1✔
151

152
        List<EntryAndTime<Artifact>> result = new ArrayList<>();
1✔
153
        for (Artifact artifact : configuration.sorter().downloads(downloadedArtifacts)) {
1✔
154
            result.add(new EntryAndTime<>(artifact, downloadedArtifacts.get(artifact)));
×
155
        }
×
156

157
        data.setDownloads(result)
1✔
158
            .setTotalDownloadTime(ArtifactDescriptor.instance(downloadedArtifacts).getTotalTimeSpentDownloadingArtifacts());
1✔
159
    }
1✔
160

161
    private void storeDownloadingArtifacts(RepositoryEvent event) {
162
        logger.debug(String.format("Received event (%s): %s", event.getClass(), event));
1✔
163
        if (event.getType() == ARTIFACT_DOWNLOADING) {
1✔
164
            statistics.startDownload(event.getArtifact());
1✔
165

166
        } else if (event.getType() == ARTIFACT_DOWNLOADED) {
1✔
167
            if (hasNoException(event)) {
1✔
168
                statistics.stopDownload(event.getArtifact());
1✔
169
            }
170
        }
171
    }
1✔
172

173
    private static boolean hasNoException(RepositoryEvent event) {
174
        List<Exception> exceptions = event.getExceptions();
1✔
175
        return exceptions == null || exceptions.isEmpty();
1✔
176
    }
177

178
    private void storeExecutionEvent(ExecutionEvent event) {
179
        logger.debug(String.format("Received event (%s): %s", event.getClass(), event));
1✔
180

181
        MavenProject currentProject = event.getSession().getCurrentProject();
1✔
182
        switch (event.getType()) {
1✔
183
            case ProjectStarted:
184
                statistics.startProject(currentProject);
1✔
185
                break;
1✔
186
            case ProjectSucceeded:
187
            case ProjectFailed:
188
                statistics.stopProject(currentProject);
1✔
189
                break;
1✔
190
            case MojoStarted:
191
                statistics.startExecution(currentProject, event.getMojoExecution());
1✔
192
                break;
1✔
193
            case MojoSucceeded:
194
            case MojoFailed:
195
                statistics.stopExecution(currentProject, event.getMojoExecution());
1✔
196
                break;
197
        }
198
    }
1✔
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc