Skip to content

Commit 6e41a88

Browse files
committed
Improve LazyProject initialization performance
added fast paths to hot methods and all property change listeners which are active during the "opening projects" phase avoid calling findFileObject() or using the lookup in event handlers if possible speeds up post-launch "opening projects" phase by up to 30% when big project groups are loaded
1 parent d753c90 commit 6e41a88

File tree

9 files changed

+131
-186
lines changed

9 files changed

+131
-186
lines changed

ide/projectui/src/org/netbeans/modules/project/ui/LazyProject.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.net.URL;
2424
import java.util.Collection;
2525
import java.util.Collections;
26-
import java.util.Iterator;
2726
import javax.swing.Action;
2827
import javax.swing.Icon;
2928
import org.netbeans.api.project.Project;
@@ -34,7 +33,6 @@
3433
import org.openide.filesystems.FileObject;
3534
import org.openide.filesystems.FileUtil;
3635
import org.openide.filesystems.URLMapper;
37-
import org.openide.loaders.DataObject;
3836
import org.openide.nodes.AbstractNode;
3937
import org.openide.nodes.Children;
4038
import org.openide.nodes.Node;
@@ -55,6 +53,7 @@ final class LazyProject implements
5553
URL url;
5654
String displayName;
5755
ExtIcon icon;
56+
private FileObject fo;
5857

5958
public LazyProject(URL url, String displayName, ExtIcon icon) {
6059
super();
@@ -65,7 +64,10 @@ public LazyProject(URL url, String displayName, ExtIcon icon) {
6564

6665
@Override
6766
public FileObject getProjectDirectory() {
68-
FileObject fo = URLMapper.findFileObject(url);
67+
if (fo != null) {
68+
return fo;
69+
}
70+
fo = URLMapper.findFileObject(url);
6971
if (fo == null) {
7072
OpenProjectList.LOGGER.warning("Project dir with " + url + " not found!");
7173
fo = FileUtil.createMemoryFileSystem().getRoot();

ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
import org.netbeans.api.project.ui.ProjectGroup;
6969
import org.netbeans.api.project.ui.ProjectGroupChangeEvent;
7070
import org.netbeans.api.project.ui.ProjectGroupChangeListener;
71-
import static org.netbeans.modules.project.ui.Bundle.*;
7271
import org.netbeans.modules.project.ui.api.UnloadedProjectInformation;
7372
import org.netbeans.modules.project.ui.groups.Group;
7473
import org.netbeans.modules.project.uiapi.ProjectOpenedTrampoline;
@@ -109,6 +108,8 @@
109108
import org.openide.util.lookup.ProxyLookup;
110109
import org.openide.windows.WindowManager;
111110

111+
import static org.netbeans.modules.project.ui.Bundle.*;
112+
112113
/**
113114
* List of projects open in the GUI.
114115
* @author Petr Hrebejk
@@ -378,7 +379,7 @@ public void run() {
378379
action = 2;
379380
try {
380381
progress.start();
381-
loadOnBackground();
382+
loadInBackground();
382383
} finally {
383384
progress.finish();
384385
}
@@ -458,14 +459,14 @@ boolean closeBeforeOpen(final Project[] arr) {
458459
"#NOI18N",
459460
"LOAD_PROJECTS_ON_START=true"
460461
})
461-
private void loadOnBackground() {
462+
private void loadInBackground() {
462463
lazilyOpenedProjects = new ArrayList<>();
463464
final boolean loadProjectsOnStart = "true".equals(Bundle.LOAD_PROJECTS_ON_START());
464465
List<URL> urls = loadProjectsOnStart ?
465466
OpenProjectListSettings.getInstance().getOpenProjectsURLs() :
466467
Collections.emptyList();
467468
final List<Project> initial = new ArrayList<>();
468-
final LinkedList<Project> projects = URLs2Projects(urls);
469+
final Collection<Project> projects = URLs2Projects(urls);
469470
OpenProjectList.MUTEX.writeAccess(new Mutex.Action<Void>() {
470471
public @Override Void run() {
471472
toOpenProjects.addAll(projects);
@@ -1269,47 +1270,28 @@ public static Project fileToProject( File projectDir ) {
12691270

12701271
// Private methods ---------------------------------------------------------
12711272

1272-
private static LinkedList<Project> URLs2Projects( Collection<URL> URLs ) {
1273-
LinkedList<Project> result = new LinkedList<Project>();
1274-
1275-
for(URL url: URLs) {
1276-
FileObject dir = URLMapper.findFileObject( url );
1277-
if ( dir != null && dir.isFolder() ) {
1273+
private static Set<Project> URLs2Projects(Collection<URL> urls) {
1274+
Set<Project> result = new LinkedHashSet<>();
1275+
1276+
for (URL url : urls) {
1277+
FileObject dir = URLMapper.findFileObject(url);
1278+
if (dir != null && dir.isFolder()) {
12781279
try {
1279-
Project p = ProjectManager.getDefault().findProject( dir );
1280-
if ( p != null && !result.contains(p)) { //#238093, #238811 if multiple entries point to the same project we end up with the same instance multiple times in the linked list. That's wrong.
1281-
result.add( p );
1280+
Project p = ProjectManager.getDefault().findProject(dir);
1281+
if (p != null && !result.contains(p)) { //#238093, #238811 if multiple entries point to the same project we end up with the same instance multiple times in the linked list. That's wrong.
1282+
result.add(p);
12821283
}
1283-
}
1284-
catch ( Throwable t ) {
1284+
} catch (Throwable t) {
12851285
//something bad happened during loading the project.
12861286
//log the problem, but allow the other projects to be load
1287-
//see issue #65900
1288-
if (t instanceof ThreadDeath) {
1289-
throw (ThreadDeath) t;
1290-
}
1291-
1287+
//see issue #65900
12921288
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, t);
12931289
}
12941290
}
12951291
}
1296-
12971292
return result;
12981293
}
12991294

1300-
private static List<URL> projects2URLs( Collection<Project> projects ) {
1301-
ArrayList<URL> URLs = new ArrayList<URL>( projects.size() );
1302-
for(Project p: projects) {
1303-
URL root = p.getProjectDirectory().toURL();
1304-
if ( root != null ) {
1305-
URLs.add( root );
1306-
}
1307-
}
1308-
1309-
return URLs;
1310-
}
1311-
1312-
13131295
private static boolean notifyOpened(Project p) {
13141296
boolean ok = true;
13151297
for (ProjectOpenedHook hook : p.getLookup().lookupAll(ProjectOpenedHook.class)) {

ide/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -412,19 +412,12 @@ final Node logicalViewForProject(
412412
// PropertyChangeListener & NodeListener impl -----------------------------------------
413413

414414
@Override
415-
public void propertyChange( PropertyChangeEvent e ) {
416-
if ( OpenProjectList.PROPERTY_OPEN_PROJECTS.equals( e.getPropertyName() ) ) {
417-
RP.post(new Runnable() {
418-
public @Override void run() {
419-
setKeys(getKeys());
420-
}
421-
});
422-
} else if( PROP_DISPLAY_NAME.equals(e.getPropertyName()) ) {
423-
RP.schedule(new Runnable() {
424-
public @Override void run() {
425-
setKeys( getKeys() );
426-
}
427-
}, 500, TimeUnit.MILLISECONDS);
415+
public void propertyChange(PropertyChangeEvent e) {
416+
switch (e.getPropertyName()) {
417+
case OpenProjectList.PROPERTY_OPEN_PROJECTS ->
418+
RP.post(() -> setKeys(getKeys()));
419+
case PROP_DISPLAY_NAME ->
420+
RP.schedule(() -> setKeys(getKeys()), 500, TimeUnit.MILLISECONDS);
428421
}
429422
}
430423

@@ -627,10 +620,14 @@ public BadgingNode(ProjectChildren ch, ProjectChildren.Pair p, Node n, boolean l
627620
this.ch = ch;
628621
this.pair = p;
629622
this.logicalView = logicalView;
630-
OpenProjectList.log(Level.FINER, "BadgingNode init {0}", toStringForLog()); // NOI18N
623+
if (LOG.isLoggable(Level.FINER)) {
624+
LOG.log(Level.FINER, "BadgingNode init {0}", toStringForLog()); // NOI18N
625+
}
631626
OpenProjectList.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjectList.getDefault()));
632627
setProjectFilesAsynch();
633-
OpenProjectList.log(Level.FINER, "BadgingNode finished {0}", toStringForLog()); // NOI18N
628+
if (LOG.isLoggable(Level.FINER)) {
629+
LOG.log(Level.FINER, "BadgingNode finished {0}", toStringForLog()); // NOI18N
630+
}
634631
AnnotationListener annotationListener = new AnnotationListener(this);
635632
annotationListener.init();
636633
result.addLookupListener(annotationListener);
@@ -655,18 +652,25 @@ private void replaceProject(Project newProj) {
655652
if (newProj == pair.project) {
656653
return;
657654
}
658-
} catch (IOException ex) {
659-
OpenProjectList.log(Level.INFO, "No project for " + pair.fo, ex); // NOI18N
660-
} catch (IllegalArgumentException ex) {
661-
OpenProjectList.log(Level.INFO, "No project for " + pair.fo, ex); // NOI18N
655+
} catch (IOException | IllegalArgumentException ex) {
656+
OpenProjectList.LOGGER.log(Level.INFO, "No project for " + pair.fo, ex); // NOI18N
662657
}
658+
}
659+
660+
// fast path before lookup. This method can be hot during startup.
661+
if (newProj != null && !pair.fo.equals(newProj.getProjectDirectory())) {
662+
return;
663+
}
663664

665+
if (OpenProjectList.LOGGER.isLoggable(Level.FINER)) {
666+
OpenProjectList.log(Level.FINER, "replacing for {0}", toStringForLog());
664667
}
665-
666-
OpenProjectList.log(Level.FINER, "replacing for {0}", toStringForLog());
668+
667669
Project p = getLookup().lookup(Project.class);
668670
if (p == null) {
669-
OpenProjectList.log(Level.FINE, "no project in lookup {0}", toStringForLog());
671+
if (OpenProjectList.LOGGER.isLoggable(Level.FINE)) {
672+
OpenProjectList.log(Level.FINE, "no project in lookup {0}", toStringForLog());
673+
}
670674
return;
671675
}
672676
FileObject fo = p.getProjectDirectory();
@@ -696,24 +700,26 @@ private void replaceProject(Project newProj) {
696700
n = Node.EMPTY;
697701
}
698702
}
699-
OpenProjectList.log(Level.FINER, "change original: {0}", n);
700-
OpenProjectList.log(Level.FINER, "children before change original: {0}", getChildren());
701-
OpenProjectList.log(Level.FINER, "delegate children before change original: {0}", getOriginal().getChildren());
703+
if (OpenProjectList.LOGGER.isLoggable(Level.FINER)) {
704+
OpenProjectList.log(Level.FINER, "change original: {0}", n);
705+
OpenProjectList.log(Level.FINER, "children before change original: {0}", getChildren());
706+
OpenProjectList.log(Level.FINER, "delegate children before change original: {0}", getOriginal().getChildren());
707+
}
702708
changeOriginal(n, true);
703-
OpenProjectList.log(Level.FINER, "delegate after change original: {0}", getOriginal());
704-
OpenProjectList.log(Level.FINER, "name after change original: {0}", getName());
705-
OpenProjectList.log(Level.FINER, "children after change original: {0}", getChildren());
706-
OpenProjectList.log(Level.FINER, "delegate children after change original: {0}", getOriginal().getChildren());
709+
if (OpenProjectList.LOGGER.isLoggable(Level.FINER)) {
710+
OpenProjectList.log(Level.FINER, "delegate after change original: {0}", getOriginal());
711+
OpenProjectList.log(Level.FINER, "name after change original: {0}", getName());
712+
OpenProjectList.log(Level.FINER, "children after change original: {0}", getChildren());
713+
OpenProjectList.log(Level.FINER, "delegate children after change original: {0}", getOriginal().getChildren());
714+
}
707715
BadgingLookup bl = (BadgingLookup) getLookup();
708716
bl.setMyLookups(n.getLookup());
709-
OpenProjectList.log(Level.FINER, "done {0}", toStringForLog());
717+
if (OpenProjectList.LOGGER.isLoggable(Level.FINER)) {
718+
OpenProjectList.log(Level.FINER, "done {0}", toStringForLog());
719+
}
710720
setProjectFilesAsynch();
711721
} else {
712-
FileObject newDir;
713-
if (newProj != null) {
714-
newDir = newProj.getProjectDirectory();
715-
} else {
716-
newDir = null;
722+
if (newProj == null) {
717723
//#228790 use RP instead of EventQueue.invokeLater, job can block on project write mutex
718724
RP.post(new Runnable() {
719725
@Override
@@ -722,7 +728,10 @@ public void run() {
722728
}
723729
});
724730
}
725-
OpenProjectList.log(Level.FINER, "wrong directories. current: " + fo + " new " + newDir);
731+
if (OpenProjectList.LOGGER.isLoggable(Level.FINER)) {
732+
FileObject newDir = newProj.getProjectDirectory();
733+
OpenProjectList.log(Level.FINER, "wrong directories. current: " + fo + " new " + newDir);
734+
}
726735
}
727736
}
728737

@@ -925,16 +934,14 @@ private Image getIcon(int type, boolean opened) {
925934
}
926935

927936
@Override
928-
public void propertyChange( PropertyChangeEvent e ) {
929-
if ( OpenProjectList.PROPERTY_MAIN_PROJECT.equals( e.getPropertyName() ) ) {
930-
mainCache = null;
931-
fireDisplayNameChange( null, null );
932-
}
933-
if ( OpenProjectList.PROPERTY_REPLACE.equals(e.getPropertyName())) {
934-
replaceProject((Project)e.getNewValue());
935-
}
936-
if (SourceGroup.PROP_CONTAINERSHIP.equals(e.getPropertyName())) {
937-
setProjectFilesAsynch();
937+
public void propertyChange(PropertyChangeEvent e) {
938+
switch (e.getPropertyName()) {
939+
case OpenProjectList.PROPERTY_MAIN_PROJECT -> {
940+
mainCache = null;
941+
fireDisplayNameChange(null, null);
942+
}
943+
case OpenProjectList.PROPERTY_REPLACE -> replaceProject((Project)e.getNewValue());
944+
case SourceGroup.PROP_CONTAINERSHIP -> setProjectFilesAsynch();
938945
}
939946
}
940947

ide/projectui/src/org/netbeans/modules/project/ui/actions/ActiveConfigAction.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -206,28 +206,27 @@ public void setUI(ComboBoxUI ui) {
206206
public ActiveConfigAction() {
207207
super();
208208
putValue("noIconInMenu", true); // NOI18N
209-
EventQueue.invokeLater(new Runnable() {
210-
public @Override void run() {
211-
initConfigListCombo();
212-
}
213-
});
214-
lst = new PropertyChangeListener() {
215-
public @Override void propertyChange(PropertyChangeEvent evt) {
216-
ProjectConfigurationProvider<?> _pcp;
217-
synchronized (ActiveConfigAction.this) {
218-
_pcp = pcp;
219-
}
220-
if (ProjectConfigurationProvider.PROP_CONFIGURATIONS.equals(evt.getPropertyName())) {
209+
EventQueue.invokeLater(this::initConfigListCombo);
210+
lst = (PropertyChangeEvent evt) -> {
211+
switch (evt.getPropertyName()) {
212+
case ProjectConfigurationProvider.PROP_CONFIGURATIONS -> {
213+
ProjectConfigurationProvider<?> _pcp;
214+
synchronized (ActiveConfigAction.this) {
215+
_pcp = pcp;
216+
}
221217
configurationsListChanged(_pcp != null ? getConfigurations(_pcp) : null);
222-
} else if (ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE.equals(evt.getPropertyName())) {
218+
}
219+
case ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE -> {
220+
ProjectConfigurationProvider<?> _pcp;
221+
synchronized (ActiveConfigAction.this) {
222+
_pcp = pcp;
223+
}
223224
activeConfigurationChanged(_pcp != null ? getActiveConfiguration(_pcp) : null);
224225
}
225226
}
226227
};
227-
looklst = new LookupListener() {
228-
public @Override void resultChanged(LookupEvent ev) {
229-
activeProjectProviderChanged();
230-
}
228+
looklst = (LookupEvent ev) -> {
229+
activeProjectProviderChanged();
231230
};
232231

233232
OpenProjectList.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjectList.getDefault()));

ide/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.netbeans.api.project.Project;
3030
import org.netbeans.api.project.ui.OpenProjects;
3131
import org.netbeans.modules.project.ui.OpenProjectList;
32-
import static org.netbeans.modules.project.ui.actions.Bundle.*;
3332
import org.netbeans.spi.project.ui.support.ProjectActionPerformer;
3433
import org.openide.DialogDisplayer;
3534
import org.openide.NotifyDescriptor;
@@ -40,6 +39,10 @@
4039
import org.openide.util.NbBundle.Messages;
4140
import org.openide.util.WeakListeners;
4241

42+
import static org.netbeans.modules.project.ui.actions.Bundle.*;
43+
import static org.netbeans.modules.project.ui.OpenProjectList.PROPERTY_MAIN_PROJECT;
44+
import static org.netbeans.modules.project.ui.OpenProjectList.PROPERTY_OPEN_PROJECTS;
45+
4346
/**
4447
* Similar to {@link ProjectAction} but has a different selection model.
4548
* First uses the main project, if set.
@@ -134,10 +137,12 @@ public void run() {
134137
}
135138
}
136139

137-
public @Override void propertyChange( PropertyChangeEvent evt ) {
138-
if (OpenProjectList.PROPERTY_MAIN_PROJECT.equals(evt.getPropertyName()) ||
139-
OpenProjectList.PROPERTY_OPEN_PROJECTS.equals(evt.getPropertyName())) {
140-
refreshView(null, false);
140+
@Override
141+
public void propertyChange(PropertyChangeEvent evt) {
142+
switch (evt.getPropertyName()) {
143+
case PROPERTY_MAIN_PROJECT, PROPERTY_OPEN_PROJECTS -> {
144+
refreshView(null, false);
145+
}
141146
}
142147
}
143148

0 commit comments

Comments
 (0)