This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 108120
Collapse All | Expand All

(-)projects/projectui/src/org/netbeans/modules/project/ui/Bundle.properties (+2 lines)
Lines 311-313 Link Here
311
UI_INIT_PROJECTS_ICON_BASE=org/netbeans/modules/project/ui/resources/open.gif
311
UI_INIT_PROJECTS_ICON_BASE=org/netbeans/modules/project/ui/resources/open.gif
312
312
313
OpeningProjectPanel.openingProjectLabel.text=Opening Project\:
313
OpeningProjectPanel.openingProjectLabel.text=Opening Project\:
314
315
MSG_ProjChInit=Initializing project...
(-)projects/projectui/src/org/netbeans/modules/project/ui/LazyProject.java (+165 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 *
24
 * Contributor(s):
25
 *
26
 * The Original Software is NetBeans. The Initial Developer of the Original
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
 * Microsystems, Inc. All Rights Reserved.
29
 *
30
 * If you wish your version of this file to be governed by only the CDDL
31
 * or only the GPL Version 2, indicate your decision by adding
32
 * "[Contributor] elects to include this software in this distribution
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34
 * single choice of license, a recipient has the option to distribute
35
 * your version of this file under either the CDDL, the GPL Version 2 or
36
 * to extend the choice of license to its licensees as provided above.
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
38
 * Version 2 license, then the option applies only if the new code is
39
 * made subject to such option by the copyright holder.
40
 */
41
package org.netbeans.modules.project.ui;
42
43
import java.awt.Image;
44
import java.beans.PropertyChangeListener;
45
import java.net.URL;
46
import java.util.Collection;
47
import java.util.Collections;
48
import java.util.Iterator;
49
import javax.swing.Icon;
50
import org.netbeans.api.project.Project;
51
import org.netbeans.api.project.ProjectInformation;
52
import org.netbeans.spi.project.ui.LogicalViewProvider;
53
import org.openide.filesystems.FileObject;
54
import org.openide.filesystems.URLMapper;
55
import org.openide.loaders.DataObject;
56
import org.openide.nodes.AbstractNode;
57
import org.openide.nodes.Children;
58
import org.openide.nodes.Node;
59
import org.openide.util.Lookup;
60
import org.openide.util.NbBundle;
61
import org.openide.util.Utilities;
62
import org.openide.util.lookup.Lookups;
63
import org.openidex.search.SearchInfo;
64
65
/**
66
 * Dummy project that shows a wait node while the real project list is 
67
 * loaded
68
 *
69
 * @author Tim Boudreau, Jaroslav Tulach
70
 */
71
final class LazyProject implements Project, ProjectInformation, SearchInfo, LogicalViewProvider {
72
    URL url;
73
    String displayName;
74
    ExtIcon icon;
75
76
    public LazyProject(URL url, String displayName, ExtIcon icon) {
77
        super();
78
        this.url = url;
79
        this.displayName = displayName;
80
        this.icon = icon;
81
    }
82
83
    public FileObject getProjectDirectory() {
84
        return URLMapper.findFileObject(url);
85
    }
86
87
    public Lookup getLookup() {
88
        return Lookups.fixed(this);
89
    }
90
91
    public String getName() {
92
        return displayName;
93
    }
94
95
    public String getDisplayName() {
96
        return displayName;
97
    }
98
99
    public Icon getIcon() {
100
        return icon.getIcon();
101
    }
102
103
    public Project getProject() {
104
        return this;
105
    }
106
107
    public void addPropertyChangeListener(PropertyChangeListener listener) {
108
    }
109
110
    public void removePropertyChangeListener(PropertyChangeListener listener) {
111
    }
112
    
113
    public boolean canSearch() {
114
        return false;
115
    }
116
117
    public Iterator<DataObject> objectsToSearch() {
118
        return Collections.<DataObject>emptyList().iterator();
119
    }
120
121
    public Node createLogicalView() {
122
        return new ProjNode(Lookups.singleton(this));
123
    }
124
125
    public Node findPath(Node root, Object target) {
126
        return null;
127
    }
128
    
129
    private final class ProjNode extends AbstractNode {
130
        public ProjNode(Lookup lookup) {
131
            super(new ProjCh(), lookup);
132
            
133
            setName(url.toExternalForm());
134
            setDisplayName(displayName);
135
        }
136
137
        @Override
138
        public Image getIcon(int type) {
139
            return Utilities.icon2Image(icon.getIcon());
140
        }
141
142
        @Override
143
        public Image getOpenedIcon(int type) {
144
            return getIcon(type);
145
        }
146
    } // end of ProjNode
147
    
148
    private final class ProjCh extends Children.Array {
149
        @Override
150
        protected Collection<Node> initCollection() {
151
            AbstractNode n = new AbstractNode(Children.LEAF);
152
            n.setName("init"); // NOI18N
153
            n.setDisplayName(NbBundle.getMessage(ProjCh.class, "MSG_ProjChInit")); 
154
            n.setIconBaseWithExtension("org/netbeans/modules/project/ui/resources/wait.gif");
155
            return Collections.singletonList((Node)n);
156
        }
157
158
        @Override
159
        protected void addNotify() {
160
            super.addNotify();
161
            OpenProjectList.preferredProject(LazyProject.this);
162
        }
163
        
164
    }
165
}
(-)projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectList.java (-13 / +153 lines)
Lines 65-70 Link Here
65
import java.util.LinkedList;
65
import java.util.LinkedList;
66
import java.util.List;
66
import java.util.List;
67
import java.util.Map;
67
import java.util.Map;
68
import java.util.Queue;
68
import java.util.Set;
69
import java.util.Set;
69
import java.util.StringTokenizer;
70
import java.util.StringTokenizer;
70
import java.util.logging.Level;
71
import java.util.logging.Level;
Lines 75-81 Link Here
75
import javax.swing.SwingUtilities;
76
import javax.swing.SwingUtilities;
76
import org.netbeans.api.progress.ProgressHandle;
77
import org.netbeans.api.progress.ProgressHandle;
77
import org.netbeans.api.progress.ProgressHandleFactory;
78
import org.netbeans.api.progress.ProgressHandleFactory;
79
import org.netbeans.api.project.FileOwnerQuery;
78
import org.netbeans.api.project.Project;
80
import org.netbeans.api.project.Project;
81
import org.netbeans.api.project.ProjectInformation;
79
import org.netbeans.api.project.ProjectManager;
82
import org.netbeans.api.project.ProjectManager;
80
import org.netbeans.api.project.ProjectUtils;
83
import org.netbeans.api.project.ProjectUtils;
81
import org.netbeans.modules.project.ui.api.UnloadedProjectInformation;
84
import org.netbeans.modules.project.ui.api.UnloadedProjectInformation;
Lines 97-106 Link Here
97
import org.openide.loaders.DataObjectNotFoundException;
100
import org.openide.loaders.DataObjectNotFoundException;
98
import org.openide.modules.ModuleInfo;
101
import org.openide.modules.ModuleInfo;
99
import org.openide.util.Lookup;
102
import org.openide.util.Lookup;
103
import org.openide.util.LookupEvent;
104
import org.openide.util.LookupListener;
100
import org.openide.util.Mutex;
105
import org.openide.util.Mutex;
101
import org.openide.util.Mutex.Action;
106
import org.openide.util.Mutex.Action;
102
import org.openide.util.NbBundle;
107
import org.openide.util.NbBundle;
103
import org.openide.util.RequestProcessor;
108
import org.openide.util.RequestProcessor;
109
import org.openide.util.Utilities;
110
import org.openide.util.WeakListeners;
111
import org.openide.util.lookup.Lookups;
104
import org.openide.windows.WindowManager;
112
import org.openide.windows.WindowManager;
105
113
106
/**
114
/**
Lines 115-120 Link Here
115
    public static final String PROPERTY_OPEN_PROJECTS = "OpenProjects";
123
    public static final String PROPERTY_OPEN_PROJECTS = "OpenProjects";
116
    public static final String PROPERTY_MAIN_PROJECT = "MainProject";
124
    public static final String PROPERTY_MAIN_PROJECT = "MainProject";
117
    public static final String PROPERTY_RECENT_PROJECTS = "RecentProjects";
125
    public static final String PROPERTY_RECENT_PROJECTS = "RecentProjects";
126
    public static final String PROPERTY_REPLACE = "ReplaceProject";
118
    
127
    
119
    private static OpenProjectList INSTANCE;
128
    private static OpenProjectList INSTANCE;
120
    
129
    
Lines 146-153 Link Here
146
    private NbProjectDeletionListener nbprojectDeleteListener = new NbProjectDeletionListener();
155
    private NbProjectDeletionListener nbprojectDeleteListener = new NbProjectDeletionListener();
147
    
156
    
148
    private PropertyChangeListener infoListener;
157
    private PropertyChangeListener infoListener;
158
    private final LoadOpenProjects LOAD;
149
    
159
    
150
    OpenProjectList() {
160
    OpenProjectList() {
161
        LOAD = new LoadOpenProjects(0);
151
        openProjects = new ArrayList<Project>();
162
        openProjects = new ArrayList<Project>();
152
        openProjectsModuleInfos = new HashMap<ModuleInfo, List<Project>>();
163
        openProjectsModuleInfos = new HashMap<ModuleInfo, List<Project>>();
153
        infoListener = new PropertyChangeListener() {
164
        infoListener = new PropertyChangeListener() {
Lines 170-191 Link Here
170
        Project[] inital = null;
181
        Project[] inital = null;
171
        synchronized ( OpenProjectList.class ) {
182
        synchronized ( OpenProjectList.class ) {
172
            if ( INSTANCE == null ) {
183
            if ( INSTANCE == null ) {
173
                needNotify = true;
174
                INSTANCE = new OpenProjectList();
184
                INSTANCE = new OpenProjectList();
175
                INSTANCE.openProjects = loadProjectList();                
185
                INSTANCE.openProjects = loadProjectList();                
176
                inital = INSTANCE.openProjects.toArray(new Project[0]);
186
                WindowManager.getDefault().invokeWhenUIReady(INSTANCE.LOAD);
177
                INSTANCE.recentTemplates = new ArrayList<String>( OpenProjectListSettings.getInstance().getRecentTemplates() );
187
            }
188
        }
189
        return INSTANCE;
190
    }
191
    
192
    static void waitProjectsFullyOpen() {
193
        getDefault().LOAD.waitFinished();
194
    }
195
196
    static void preferredProject(Project lazyP) {
197
        if (lazyP != null) {
198
            getDefault().LOAD.preferredProject(lazyP);
199
        }
200
    }
201
    
202
    
203
    private final class LoadOpenProjects implements Runnable, LookupListener {
204
        final RequestProcessor RP = new RequestProcessor("Load Open Projects"); // NOI18N
205
        final RequestProcessor.Task TASK = RP.create(this);
206
        private int action;
207
        private LinkedList<Project> toOpenProjects = new LinkedList<Project>();
208
        private List<Project> openedProjects;
209
        private List<String> recentTemplates;
210
        private Project mainProject;
211
        private Lookup.Result<FileObject> currentFiles;
212
        
213
        public LoadOpenProjects(int a) {
214
            action = a;
215
            currentFiles = Utilities.actionsGlobalContext().lookupResult(FileObject.class);
216
            currentFiles.addLookupListener(WeakListeners.create(LookupListener.class, this, currentFiles));
217
            resultChanged(null);
218
        }
219
220
        final void waitFinished() {
221
            if (EventQueue.isDispatchThread()) {
222
                if (action == 0) {
223
                    run();
224
                }
225
            }
226
            TASK.waitFinished();
227
        }
228
        
229
        public void run() {
230
            switch (action) {
231
                case 0: 
232
                    action = 1;
233
                    TASK.schedule(0);
234
                    return;
235
                case 1:
236
                    action = 2;
237
                    loadOnBackground();
238
                    updateGlobalState();
239
                    return;
240
                case 2:
241
                    // finished, oK
242
                    return;
243
                default:
244
                    throw new IllegalStateException("unknown action: " + action);
245
            }
246
        }
247
248
        final void preferredProject(Project lazyP) {
249
            synchronized (toOpenProjects) {
250
                for (Project p : toOpenProjects) {
251
                    if (p.getProjectDirectory().equals(lazyP.getProjectDirectory())) {
252
                        toOpenProjects.remove(p);
253
                        toOpenProjects.addFirst(p);
254
                        return;
255
                    }
256
                }
257
            }
258
        }
259
        
260
        private void updateGlobalState() {
261
            INSTANCE.openProjects = openedProjects;
262
            INSTANCE.mainProject = mainProject;
263
            INSTANCE.recentTemplates = recentTemplates;
264
            
265
            INSTANCE.pchSupport.firePropertyChange(PROPERTY_OPEN_PROJECTS, new Project[0], openedProjects.toArray(new Project[0]));
266
            INSTANCE.pchSupport.firePropertyChange(PROPERTY_MAIN_PROJECT, null, INSTANCE.mainProject);
267
        }
268
            
269
        private void loadOnBackground() {
270
            openedProjects = new ArrayList<Project>();
271
            List<URL> URLs = OpenProjectListSettings.getInstance().getOpenProjectsURLs();
272
            toOpenProjects.addAll(URLs2Projects(URLs));
273
            Project[] inital;
274
            synchronized (toOpenProjects) {
275
                inital = toOpenProjects.toArray(new Project[0]);
276
            }
277
            recentTemplates = new ArrayList<String>( OpenProjectListSettings.getInstance().getRecentTemplates() );
178
                URL mainProjectURL = OpenProjectListSettings.getInstance().getMainProjectURL();
278
                URL mainProjectURL = OpenProjectListSettings.getInstance().getMainProjectURL();
179
                // Load recent project list
279
                // Load recent project list
180
                INSTANCE.recentProjects.load();
280
                INSTANCE.recentProjects.load();
181
                for( Iterator it = INSTANCE.openProjects.iterator(); it.hasNext(); ) {
281
            synchronized (toOpenProjects) {
282
                for( Iterator it = toOpenProjects.iterator(); it.hasNext(); ) {
182
                    Project p = (Project)it.next();
283
                    Project p = (Project)it.next();
183
                    INSTANCE.addModuleInfo(p);
284
                    INSTANCE.addModuleInfo(p);
184
                    // Set main project
285
                    // Set main project
185
                    try {
286
                    try {
186
                        if ( mainProjectURL != null && 
287
                        if ( mainProjectURL != null && 
187
                             mainProjectURL.equals( p.getProjectDirectory().getURL() ) ) {
288
                             mainProjectURL.equals( p.getProjectDirectory().getURL() ) ) {
188
                            INSTANCE.mainProject = p;
289
                            mainProject = p;
189
                        }
290
                        }
190
                    }
291
                    }
191
                    catch( FileStateInvalidException e ) {
292
                    catch( FileStateInvalidException e ) {
Lines 193-213 Link Here
193
                    }
294
                    }
194
                }          
295
                }          
195
            }
296
            }
297
            for (;;) {
298
                Project p;
299
                synchronized (toOpenProjects) {
300
                    if (toOpenProjects.isEmpty()) {
301
                        break;
196
        }
302
        }
197
        if ( needNotify ) {
303
                    p = toOpenProjects.remove();
198
            //#68738: a project may open other projects in its ProjectOpenedHook:
304
                }
199
            for(Project p: new ArrayList<Project>(INSTANCE.openProjects)) {
305
                openedProjects.add(p);
200
                notifyOpened(p);             
306
                notifyOpened(p);             
307
                PropertyChangeEvent ev = new PropertyChangeEvent(this, PROPERTY_REPLACE, null, p);
308
                pchSupport.firePropertyChange(ev);
201
            }
309
            }
202
            
310
            
203
        }
204
        if (inital != null) {
311
        if (inital != null) {
205
            log(createRecord("UI_INIT_PROJECTS", inital));
312
            log(createRecord("UI_INIT_PROJECTS", inital));
206
        }
313
        }
207
        
314
        
208
        return INSTANCE;
209
    }
315
    }
210
    
316
    
317
        public void resultChanged(LookupEvent ev) {
318
            for (FileObject fileObject : currentFiles.allInstances()) {
319
                Project p = FileOwnerQuery.getOwner(fileObject);
320
                OpenProjectList.preferredProject(p);
321
            }
322
323
        }
324
    }
325
    
211
    public void open( Project p ) {
326
    public void open( Project p ) {
212
        open( new Project[] {p}, false );
327
        open( new Project[] {p}, false );
213
    }
328
    }
Lines 290-295 Link Here
290
    
405
    
291
    private void doOpen(Project[] projects, boolean openSubprojects, ProgressHandle handle, OpeningProjectPanel panel) {
406
    private void doOpen(Project[] projects, boolean openSubprojects, ProgressHandle handle, OpeningProjectPanel panel) {
292
        assert !Arrays.asList(projects).contains(null) : "Projects can't be null";
407
        assert !Arrays.asList(projects).contains(null) : "Projects can't be null";
408
        LOAD.waitFinished();
293
            
409
            
294
        boolean recentProjectsChanged = false;
410
        boolean recentProjectsChanged = false;
295
        int  maxWork = 1000;
411
        int  maxWork = 1000;
Lines 401-406 Link Here
401
    }
517
    }
402
       
518
       
403
    public void close( Project projects[], boolean notifyUI ) {
519
    public void close( Project projects[], boolean notifyUI ) {
520
        LOAD.waitFinished();
404
        if (!ProjectUtilities.closeAllDocuments (projects, notifyUI )) {
521
        if (!ProjectUtilities.closeAllDocuments (projects, notifyUI )) {
405
            return;
522
            return;
406
        }
523
        }
Lines 618-625 Link Here
618
    
735
    
619
    // Private methods ---------------------------------------------------------
736
    // Private methods ---------------------------------------------------------
620
    
737
    
621
    private static List<Project> URLs2Projects( Collection<URL> URLs ) {
738
    private static LinkedList<Project> URLs2Projects( Collection<URL> URLs ) {
622
        ArrayList<Project> result = new ArrayList<Project>( URLs.size() );
739
        LinkedList<Project> result = new LinkedList<Project>();
623
            
740
            
624
        for(URL url: URLs) {
741
        for(URL url: URLs) {
625
            FileObject dir = URLMapper.findFileObject( url );
742
            FileObject dir = URLMapper.findFileObject( url );
Lines 730-737 Link Here
730
    
847
    
731
    private static List<Project> loadProjectList() {               
848
    private static List<Project> loadProjectList() {               
732
        List<URL> URLs = OpenProjectListSettings.getInstance().getOpenProjectsURLs();
849
        List<URL> URLs = OpenProjectListSettings.getInstance().getOpenProjectsURLs();
733
        List<Project> projects = URLs2Projects( URLs );
850
        List<String> names = OpenProjectListSettings.getInstance().getOpenProjectsDisplayNames();
851
        List<ExtIcon> icons = OpenProjectListSettings.getInstance().getOpenProjectsIcons();
852
        List<Project> projects = new ArrayList<Project>();
734
        
853
        
854
        Iterator<URL> urlIt = URLs.iterator();
855
        Iterator<String> namesIt = names.iterator();
856
        Iterator<ExtIcon> iconIt = icons.iterator();
857
        
858
        while(urlIt.hasNext() && namesIt.hasNext() && iconIt.hasNext()) {
859
            projects.add(new LazyProject(urlIt.next(), namesIt.next(), iconIt.next()));
860
        }
861
        
862
        //List<Project> projects = URLs2Projects( URLs );
863
        
735
        return projects;
864
        return projects;
736
    }
865
    }
737
    
866
    
Lines 739-745 Link Here
739
    private static void saveProjectList( List<Project> projects ) {        
868
    private static void saveProjectList( List<Project> projects ) {        
740
        List<URL> URLs = projects2URLs( projects );
869
        List<URL> URLs = projects2URLs( projects );
741
        OpenProjectListSettings.getInstance().setOpenProjectsURLs( URLs );
870
        OpenProjectListSettings.getInstance().setOpenProjectsURLs( URLs );
871
        List<String> names = new ArrayList<String>();
872
        List<ExtIcon> icons = new ArrayList<ExtIcon>();
873
        for (Iterator<Project> it = projects.iterator(); it.hasNext(); ) {
874
            ProjectInformation prjInfo = ProjectUtils.getInformation(it.next());
875
            names.add(prjInfo.getDisplayName());
876
            ExtIcon extIcon = new ExtIcon();
877
            extIcon.setIcon(prjInfo.getIcon());
878
            icons.add(extIcon);
742
    }
879
    }
880
        OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names);
881
        OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons);
882
    }
743
    
883
    
744
    private static void saveMainProject( Project mainProject ) {        
884
    private static void saveMainProject( Project mainProject ) {        
745
        try {
885
        try {
(-)projects/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java (+21 lines)
Lines 51-56 Link Here
51
import java.util.prefs.Preferences;
51
import java.util.prefs.Preferences;
52
import javax.swing.filechooser.FileSystemView;
52
import javax.swing.filechooser.FileSystemView;
53
import org.openide.filesystems.FileUtil;
53
import org.openide.filesystems.FileUtil;
54
import org.openide.util.Exceptions;
54
import org.openide.util.NbBundle;
55
import org.openide.util.NbBundle;
55
import org.openide.util.NbPreferences;
56
import org.openide.util.NbPreferences;
56
57
Lines 68-73 Link Here
68
    private static final String MAIN_PROJECT_URL = "mainProjectURL"; //NOI18N -URL
69
    private static final String MAIN_PROJECT_URL = "mainProjectURL"; //NOI18N -URL
69
    private static final String OPEN_AS_MAIN = "openAsMain"; //NOI18N - boolean
70
    private static final String OPEN_AS_MAIN = "openAsMain"; //NOI18N - boolean
70
    private static final String OPEN_PROJECTS_URLS = "openProjectsURLs"; //NOI18N - List of URLs
71
    private static final String OPEN_PROJECTS_URLS = "openProjectsURLs"; //NOI18N - List of URLs
72
    private static final String OPEN_PROJECTS_DISPLAY_NAMES = "openProjectsDisplayNames"; //NOI18N - List of names
73
    private static final String OPEN_PROJECTS_ICONS = "openProjectsIcons"; //NOI18N - List of icons
71
    private static final String OPEN_SUBPROJECTS = "openSubprojects"; //NOI18N - boolean
74
    private static final String OPEN_SUBPROJECTS = "openSubprojects"; //NOI18N - boolean
72
    private static final String PROP_PROJECTS_FOLDER = "projectsFolder"; //NOI18N - String
75
    private static final String PROP_PROJECTS_FOLDER = "projectsFolder"; //NOI18N - String
73
    private static final String RECENT_PROJECTS_URLS = "recentProjectsURLs"; //NOI18N List of URLs
76
    private static final String RECENT_PROJECTS_URLS = "recentProjectsURLs"; //NOI18N List of URLs
Lines 198-204 Link Here
198
    public void setOpenProjectsURLs( List<URL> list ) {
201
    public void setOpenProjectsURLs( List<URL> list ) {
199
        setURLList( OPEN_PROJECTS_URLS, list);
202
        setURLList( OPEN_PROJECTS_URLS, list);
200
    }
203
    }
204
    public List<String> getOpenProjectsDisplayNames() {
205
        return getStringList(OPEN_PROJECTS_DISPLAY_NAMES);
206
    }
201
    
207
    
208
    public void setOpenProjectsDisplayNames( List<String> list ) {
209
        setStringList( OPEN_PROJECTS_DISPLAY_NAMES, list);
210
    }
211
    public List<ExtIcon> getOpenProjectsIcons() {
212
        return getIconList(OPEN_PROJECTS_ICONS);
213
    }
214
215
    public void setOpenProjectsIcons( List<ExtIcon> list ) {
216
        try {
217
            setIconList(OPEN_PROJECTS_ICONS, list);
218
        } catch (IOException ex) {
219
            Exceptions.printStackTrace(ex);
220
        }
221
    }
222
    
202
    public boolean isOpenSubprojects() {        
223
    public boolean isOpenSubprojects() {        
203
        return getPreferences().getBoolean( OPEN_SUBPROJECTS, false);
224
        return getPreferences().getBoolean( OPEN_SUBPROJECTS, false);
204
    }
225
    }
(-)projects/projectui/src/org/netbeans/modules/project/ui/ProjectsRootNode.java (-37 / +133 lines)
Lines 63-68 Link Here
63
import javax.swing.event.ChangeListener;
63
import javax.swing.event.ChangeListener;
64
import org.netbeans.api.project.FileOwnerQuery;
64
import org.netbeans.api.project.FileOwnerQuery;
65
import org.netbeans.api.project.Project;
65
import org.netbeans.api.project.Project;
66
import org.netbeans.api.project.ProjectManager;
66
import org.netbeans.api.project.ProjectUtils;
67
import org.netbeans.api.project.ProjectUtils;
67
import org.netbeans.api.project.SourceGroup;
68
import org.netbeans.api.project.SourceGroup;
68
import org.netbeans.api.project.Sources;
69
import org.netbeans.api.project.Sources;
Lines 79-84 Link Here
79
import org.openide.nodes.Children;
80
import org.openide.nodes.Children;
80
import org.openide.nodes.FilterNode;
81
import org.openide.nodes.FilterNode;
81
import org.openide.nodes.Node;
82
import org.openide.nodes.Node;
83
import org.openide.util.Lookup;
82
import org.openide.util.NbBundle;
84
import org.openide.util.NbBundle;
83
import org.openide.util.RequestProcessor;
85
import org.openide.util.RequestProcessor;
84
import org.openide.util.WeakListeners;
86
import org.openide.util.WeakListeners;
Lines 213-219 Link Here
213
    
215
    
214
    // XXX Needs to listen to project rename
216
    // XXX Needs to listen to project rename
215
    // However project rename is currently disabled so it is not a big deal
217
    // However project rename is currently disabled so it is not a big deal
216
    static class ProjectChildren extends Children.Keys<Project> implements ChangeListener, PropertyChangeListener {
218
    static class ProjectChildren extends Children.Keys<ProjectChildren.Pair> implements ChangeListener, PropertyChangeListener {
217
        
219
        
218
        private java.util.Map <Sources,Reference<Project>> sources2projects = new WeakHashMap<Sources,Reference<Project>>();
220
        private java.util.Map <Sources,Reference<Project>> sources2projects = new WeakHashMap<Sources,Reference<Project>>();
219
        
221
        
Lines 221-292 Link Here
221
        
223
        
222
        public ProjectChildren( int type ) {
224
        public ProjectChildren( int type ) {
223
            this.type = type;
225
            this.type = type;
224
            OpenProjectList.getDefault().addPropertyChangeListener( this );
225
        }
226
        }
226
        
227
        
227
        // Children.Keys impl --------------------------------------------------
228
        // Children.Keys impl --------------------------------------------------
228
        
229
        
230
        @Override
229
        public void addNotify() {            
231
        public void addNotify() {            
232
            OpenProjectList.getDefault().addPropertyChangeListener(this);
230
            setKeys( getKeys() );
233
            setKeys( getKeys() );
231
        }
234
        }
232
        
235
        
236
        @Override
233
        public void removeNotify() {
237
        public void removeNotify() {
238
            OpenProjectList.getDefault().removePropertyChangeListener(this);
234
            for (Sources sources : sources2projects.keySet()) {
239
            for (Sources sources : sources2projects.keySet()) {
235
                sources.removeChangeListener( this );                
240
                sources.removeChangeListener( this );                
236
            }
241
            }
237
            sources2projects.clear();
242
            sources2projects.clear();
238
            setKeys(Collections.<Project>emptySet());
243
            setKeys(Collections.<Pair>emptySet());
239
        }
244
        }
240
        
245
        
241
        protected Node[] createNodes(Project project) {
246
        protected Node[] createNodes(Pair p) {
242
            LogicalViewProvider lvp = project.getLookup().lookup(LogicalViewProvider.class);
247
            Project project = p.project;
243
            
248
            
244
            Node nodes[] = null;
249
            Node origNodes[] = null;
245
            boolean projectInLookup = true;
250
            boolean[] projectInLookup = new boolean[1];
251
            projectInLookup[0] = true;
246
                        
252
                        
247
            if ( type == PHYSICAL_VIEW ) {
253
            if ( type == PHYSICAL_VIEW ) {
248
                Sources sources = ProjectUtils.getSources( project );
254
                Sources sources = ProjectUtils.getSources( project );
249
                sources.removeChangeListener( this );
255
                sources.removeChangeListener( this );
250
                sources.addChangeListener( this );
256
                sources.addChangeListener( this );
251
                sources2projects.put( sources, new WeakReference<Project>( project ) );
257
                sources2projects.put( sources, new WeakReference<Project>( project ) );
252
                nodes = PhysicalView.createNodesForProject( project );
258
                origNodes = PhysicalView.createNodesForProject( project );
259
            } else {
260
                origNodes = new Node[] { logicalViewForProject(project, projectInLookup) };
253
            }            
261
            }            
254
            else if ( lvp == null ) {
262
255
                ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply a LogicalViewProvider in its lookup"); // NOI18N
263
            Node[] badgedNodes = new Node[ origNodes.length ];
256
                Sources sources = ProjectUtils.getSources( project );
264
            for( int i = 0; i < origNodes.length; i++ ) {
257
                sources.removeChangeListener( this );
265
                if ( type == PHYSICAL_VIEW && !PhysicalView.isProjectDirNode( origNodes[i] ) ) {
258
                sources.addChangeListener( this );
266
                    // Don't badge external sources
259
                nodes = PhysicalView.createNodesForProject( project );
267
                    badgedNodes[i] = origNodes[i];
260
                if ( nodes.length > 0 ) {
261
                    nodes = new Node[] { nodes[0] };
262
                }
268
                }
263
                else {
269
                else {
264
                    nodes = new Node[] { Node.EMPTY };
270
                    badgedNodes[i] = new BadgingNode( origNodes[i],
271
                                                      type == LOGICAL_VIEW  && projectInLookup[0]);
265
                }
272
                }
266
            }
273
            }
267
            else {
274
                        
268
                nodes = new Node[] { lvp.createLogicalView() };
275
            return badgedNodes;
269
                if (nodes[0].getLookup().lookup(Project.class) != project) {
276
        }        
277
        
278
        private Node logicalViewForProject(Project project, boolean[] projectInLookup) {
279
            Node node;
280
            
281
            LogicalViewProvider lvp = project.getLookup().lookup(LogicalViewProvider.class);
282
            
283
            if ( lvp == null ) {
284
                ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply a LogicalViewProvider in its lookup"); // NOI18N
285
                Sources sources = ProjectUtils.getSources(project);
286
                sources.removeChangeListener(this);
287
                sources.addChangeListener(this);
288
                Node[] physical = PhysicalView.createNodesForProject(project);
289
                if (physical.length > 0) {
290
                    node = physical[0];
291
                } else {
292
                    node = Node.EMPTY;
293
                }
294
            } else {
295
                node = lvp.createLogicalView();
296
                if (node.getLookup().lookup(Project.class) != project) {
270
                    // Various actions, badging, etc. are not going to work.
297
                    // Various actions, badging, etc. are not going to work.
271
                    ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply itself in the lookup of the root node of its own logical view"); // NOI18N
298
                    ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - project " + ProjectUtils.getInformation(project).getName() + " failed to supply itself in the lookup of the root node of its own logical view"); // NOI18N
272
                    //#114664
299
                    //#114664
273
                    projectInLookup = false;
300
                    if (projectInLookup != null) {
301
                        projectInLookup[0] = false;
274
                }
302
                }
275
            }
303
            }
276
277
            Node[] badgedNodes = new Node[ nodes.length ];
278
            for( int i = 0; i < nodes.length; i++ ) {
279
                if ( type == PHYSICAL_VIEW && !PhysicalView.isProjectDirNode( nodes[i] ) ) {
280
                    // Don't badge external sources
281
                    badgedNodes[i] = nodes[i];
282
                }
304
                }
283
                else {
284
                    badgedNodes[i] = new BadgingNode( nodes[i],
285
                                                      type == LOGICAL_VIEW  && projectInLookup);
286
                }
287
            }
288
                        
305
                        
289
            return badgedNodes;
306
            return node;
290
        }        
307
        }        
291
        
308
        
292
        // PropertyChangeListener impl -----------------------------------------
309
        // PropertyChangeListener impl -----------------------------------------
Lines 315-336 Link Here
315
            // Fix for 50259, callers sometimes hold locks
332
            // Fix for 50259, callers sometimes hold locks
316
            SwingUtilities.invokeLater( new Runnable() {
333
            SwingUtilities.invokeLater( new Runnable() {
317
                public void run() {
334
                public void run() {
318
                    refreshKey( project );
335
                    refreshKey( new Pair(project, project.getProjectDirectory()) );
319
                }
336
                }
320
            } );
337
            } );
321
        }
338
        }
322
                                
339
                                
323
        // Own methods ---------------------------------------------------------
340
        // Own methods ---------------------------------------------------------
324
        
341
        
325
        public Collection<Project> getKeys() {
342
        public Collection<Pair> getKeys() {
326
            List<Project> projects = Arrays.asList( OpenProjectList.getDefault().getOpenProjects() );
343
            List<Project> projects = Arrays.asList( OpenProjectList.getDefault().getOpenProjects() );
327
            Collections.sort( projects, OpenProjectList.PROJECT_BY_DISPLAYNAME );
344
            Collections.sort( projects, OpenProjectList.PROJECT_BY_DISPLAYNAME );
328
            
345
            
329
            return projects;
346
            List<Pair> dirs = Arrays.asList( new Pair[projects.size()] );
347
            
348
            for (int i = 0; i < projects.size(); i++) {
349
                Project project = projects.get(i);
350
                dirs.set(i, new Pair(project, project.getProjectDirectory()));
330
        }
351
        }
331
                                                
352
                                                
353
            
354
            return dirs;
332
    }
355
    }
333
        
356
        
357
        /** Object that comparers two projects just by their directory.
358
         * This allows to replace a LazyProject with real one without discarding
359
         * the nodes.
360
         */
361
        private static final class Pair extends Object {
362
            public final Project project;
363
            public final FileObject fo;
364
365
            public Pair(Project project, FileObject fo) {
366
                this.project = project;
367
                this.fo = fo;
368
            }
369
370
            @Override
371
            public boolean equals(Object obj) {
372
                if (obj == null) {
373
                    return false;
374
                }
375
                if (getClass() != obj.getClass()) {
376
                    return false;
377
                }
378
                final Pair other = (Pair) obj;
379
                if (this.fo != other.fo && (this.fo == null || !this.fo.equals(other.fo))) {
380
                    return false;
381
                }
382
                return true;
383
            }
384
385
            @Override
386
            public int hashCode() {
387
                int hash = 7;
388
                hash = 53 * hash + (this.fo != null ? this.fo.hashCode() : 0);
389
                return hash;
390
            }
391
        }
392
                                                
393
    }
394
        
334
    private static final class BadgingNode extends FilterNode implements PropertyChangeListener, Runnable, FileStatusListener {
395
    private static final class BadgingNode extends FilterNode implements PropertyChangeListener, Runnable, FileStatusListener {
335
396
336
        private static String badgedNamePattern = NbBundle.getMessage(ProjectsRootNode.class, "LBL_MainProject_BadgedNamePattern");
397
        private static String badgedNamePattern = NbBundle.getMessage(ProjectsRootNode.class, "LBL_MainProject_BadgedNamePattern");
Lines 341-347 Link Here
341
        private volatile boolean nameChange;
402
        private volatile boolean nameChange;
342
403
343
        public BadgingNode(Node n, boolean addSearchInfo) {
404
        public BadgingNode(Node n, boolean addSearchInfo) {
344
            super(n, null, addSearchInfo ? new ProxyLookup(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class)))) : n.getLookup());
405
            super(n, null, addSearchInfo ? badgingLookup(n) : n.getLookup());
345
            OpenProjectList.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjectList.getDefault()));
406
            OpenProjectList.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, OpenProjectList.getDefault()));
346
            Project proj = getOriginal().getLookup().lookup(Project.class);
407
            Project proj = getOriginal().getLookup().lookup(Project.class);
347
            if (proj != null) {
408
            if (proj != null) {
Lines 362-367 Link Here
362
            }
423
            }
363
        }
424
        }
364
425
426
        private static Lookup badgingLookup(Node n) {
427
            return new BadgingLookup(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class))));
428
        }
429
        
430
        private void updateLookup(Node n) {
431
            if (getLookup() instanceof BadgingLookup) {
432
                BadgingLookup bl = (BadgingLookup)getLookup();
433
                bl.setMyLookups(n.getLookup(), Lookups.singleton(alwaysSearchableSearchInfo(n.getLookup().lookup(Project.class))));
434
            }
435
        }
436
        
365
        public void run() {
437
        public void run() {
366
            if (nameChange) {
438
            if (nameChange) {
367
                fireDisplayNameChange(null, null);
439
                fireDisplayNameChange(null, null);
Lines 433-446 Link Here
433
            if ( OpenProjectList.PROPERTY_MAIN_PROJECT.equals( e.getPropertyName() ) ) {
505
            if ( OpenProjectList.PROPERTY_MAIN_PROJECT.equals( e.getPropertyName() ) ) {
434
                fireDisplayNameChange( null, null );
506
                fireDisplayNameChange( null, null );
435
            }
507
            }
508
            if ( OpenProjectList.PROPERTY_REPLACE.equals(e.getPropertyName())) {
509
                Project p = getLookup().lookup(Project.class);
510
                if (p == null) {
511
                    return;
436
        }
512
        }
513
                FileObject fo = p.getProjectDirectory();
514
                Project newProj = (Project)e.getNewValue();
515
                assert newProj != null;
516
                if (newProj.getProjectDirectory().equals(fo)) {
517
                    ProjectChildren ch = (ProjectChildren)getParentNode().getChildren();
518
                    Node n = ch.logicalViewForProject(newProj, null);
519
                    changeOriginal(n, true);
520
                    updateLookup(n);
521
                }
522
            }
523
        }
437
524
438
        private boolean isMain() {
525
        private boolean isMain() {
439
            Project p = getLookup().lookup(Project.class);
526
            Project p = getLookup().lookup(Project.class);
440
            return p != null && OpenProjectList.getDefault().isMainProject( p );
527
            return p != null && OpenProjectList.getDefault().isMainProject( p );
441
        }
528
        }
442
        
529
        
530
    } // end of BadgingNode
531
    
532
    private static final class BadgingLookup extends ProxyLookup {
533
        public BadgingLookup(Lookup... lkps) {
534
            super(lkps);
443
    }
535
    }
536
        public void setMyLookups(Lookup... lkps) {
537
            setLookups(lkps);
538
        }
539
    } // end of BadgingLookup
444
    
540
    
445
    /**
541
    /**
446
     * Produce a {@link SearchInfo} variant that is always searchable, for speed.
542
     * Produce a {@link SearchInfo} variant that is always searchable, for speed.
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectListTest.java (+5 lines)
Lines 198-203 Link Here
198
    
198
    
199
    public void testSerialize() throws Exception {
199
    public void testSerialize() throws Exception {
200
        testOpen();
200
        testOpen();
201
        
202
        OpenProjectList.waitProjectsFullyOpen();
201
        Field f = OpenProjectList.class.getDeclaredField("INSTANCE");
203
        Field f = OpenProjectList.class.getDeclaredField("INSTANCE");
202
        f.setAccessible(true);
204
        f.setAccessible(true);
203
        f.set(null, null);
205
        f.set(null, null);
Lines 205-210 Link Here
205
        CharSequence whatIsLoggedWhenDeserializing = Log.enable("org.netbeans.ui", Level.FINE);
207
        CharSequence whatIsLoggedWhenDeserializing = Log.enable("org.netbeans.ui", Level.FINE);
206
        
208
        
207
        Project[] arr = OpenProjectList.getDefault().getOpenProjects();
209
        Project[] arr = OpenProjectList.getDefault().getOpenProjects();
210
        OpenProjectList.waitProjectsFullyOpen();
211
        arr = OpenProjectList.getDefault().getOpenProjects();
212
        
208
        assertEquals("One", 1, arr.length);
213
        assertEquals("One", 1, arr.length);
209
        Pattern p = Pattern.compile("Initializing.*1.*TestProject", Pattern.MULTILINE | Pattern.DOTALL);
214
        Pattern p = Pattern.compile("Initializing.*1.*TestProject", Pattern.MULTILINE | Pattern.DOTALL);
210
        Matcher m = p.matcher(whatIsLoggedWhenDeserializing);
215
        Matcher m = p.matcher(whatIsLoggedWhenDeserializing);
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/OpenProjectsTrampolineImplTest.java (+1 lines)
Lines 80-85 Link Here
80
//        mysteryproject = scratch.createFolder("mystery");
80
//        mysteryproject = scratch.createFolder("mystery");
81
        TestUtil.setLookup(Lookups.singleton(TestUtil.testProjectFactory()));
81
        TestUtil.setLookup(Lookups.singleton(TestUtil.testProjectFactory()));
82
        pm = ProjectManager.getDefault();
82
        pm = ProjectManager.getDefault();
83
        OpenProjectList.waitProjectsFullyOpen();
83
    }
84
    }
84
    
85
    
85
    protected void tearDown() throws Exception {
86
    protected void tearDown() throws Exception {
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java (-1 / +1 lines)
Lines 80-86 Link Here
80
    static {
80
    static {
81
        System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
81
        System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
82
    }
82
    }
83
    */
83
    /**/
84
    
84
    
85
    private static final String NAVIGATOR_MODE = "navigator";
85
    private static final String NAVIGATOR_MODE = "navigator";
86
    
86
    
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredFromContextOpenTest.java (+277 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 * 
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 * 
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 * 
35
 * Contributor(s):
36
 * 
37
 * Portions Copyrighted 2007 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.modules.project.ui;
41
42
import java.awt.EventQueue;
43
import java.beans.PropertyChangeEvent;
44
import java.io.IOException;
45
import java.net.URL;
46
import java.util.ArrayList;
47
import java.util.EventObject;
48
import java.util.List;
49
import java.util.concurrent.CountDownLatch;
50
import javax.swing.Action;
51
import junit.framework.TestCase;
52
import org.netbeans.api.project.Project;
53
import org.netbeans.api.project.ProjectManager;
54
import org.netbeans.junit.MockServices;
55
import org.netbeans.junit.NbTestCase;
56
import org.netbeans.modules.project.ui.actions.TestSupport;
57
import org.netbeans.spi.project.ui.ProjectOpenedHook;
58
import org.openide.filesystems.FileObject;
59
import org.openide.filesystems.FileUtil;
60
import org.openide.filesystems.URLMapper;
61
import org.openide.nodes.Node;
62
import org.openide.nodes.Node.Handle;
63
import org.openide.nodes.NodeEvent;
64
import org.openide.nodes.NodeListener;
65
import org.openide.nodes.NodeMemberEvent;
66
import org.openide.nodes.NodeReorderEvent;
67
import org.openide.util.ContextGlobalProvider;
68
import org.openide.util.Exceptions;
69
import org.openide.util.Lookup;
70
import org.openide.util.lookup.Lookups;
71
72
/** 
73
 *
74
 * @author Jaroslav Tulach <jtulach@netbeans.org>
75
 */
76
public class ProjectsRootNodePreferredFromContextOpenTest extends NbTestCase {
77
    CountDownLatch first;
78
    CountDownLatch middle;
79
    CountDownLatch rest;
80
    
81
    public ProjectsRootNodePreferredFromContextOpenTest(String testName) {
82
        super(testName);
83
    }            
84
85
    @Override
86
    protected void setUp() throws Exception {
87
        clearWorkDir();
88
        
89
        MockServices.setServices(TestSupport.TestProjectFactory.class, ContextProv.class);
90
        
91
        FileObject workDir = FileUtil.toFileObject(getWorkDir());
92
        assertNotNull(workDir);
93
        
94
        first = new CountDownLatch(1);
95
        middle = new CountDownLatch(1);
96
        rest = new CountDownLatch(2);
97
        
98
        List<URL> list = new ArrayList<URL>();
99
        List<ExtIcon> icons = new ArrayList<ExtIcon>();
100
        List<String> names = new ArrayList<String>();
101
        for (int i = 0; i < 10; i++) {
102
            FileObject prj = TestSupport.createTestProject(workDir, "prj" + i);
103
            URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL);
104
            list.add(url);
105
            names.add(url.toExternalForm());
106
            icons.add(new ExtIcon());
107
            TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj);
108
            assertNotNull("Project found", tmp);
109
            CountDownLatch down = i == 0 ? first : (i == 5 ? middle : rest);
110
            tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down)));
111
        }
112
        
113
        OpenProjectListSettings.getInstance().setOpenProjectsURLs(list);
114
        OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names);
115
        OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons);
116
    }
117
118
    @Override
119
    protected void tearDown() throws Exception {
120
        super.tearDown();
121
    }
122
123
    public void testPreferencesInOpenCanBeChanged() throws InterruptedException, IOException {
124
        Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW);
125
        L listener = new L();
126
        logicalView.addNodeListener(listener);
127
        
128
        assertEquals("10 children", 10, logicalView.getChildren().getNodesCount());
129
        listener.assertEvents("None", 0);
130
        assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened);
131
        
132
        for (Node n : logicalView.getChildren().getNodes()) {
133
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
134
            assertNull("No project of this type, yet", p);
135
        }
136
        
137
        Node midNode = logicalView.getChildren().getNodes()[5];
138
        {
139
            TestSupport.TestProject p = midNode.getLookup().lookup(TestSupport.TestProject.class);
140
            assertNull("No project of this type, yet", p);
141
        }
142
        Project lazyP = midNode.getLookup().lookup(Project.class);
143
        assertNotNull("Some project is found", lazyP);
144
        assertEquals("It is lazy project", LazyProject.class, lazyP.getClass());
145
        
146
        middle.countDown();
147
        // not necessary, but to ensure middle really does not run
148
        Thread.sleep(300);
149
        assertEquals("Still no processing", 0, TestProjectOpenedHookImpl.opened);
150
        
151
        // make a file of some project selected, that 
152
        // shall trigger OpenProjectList.preferredProject(lazyP);
153
        FileObject create = FileUtil.createData(lazyP.getProjectDirectory(), "test.txt");
154
        ContextProv.assign(Lookups.singleton(create));
155
        first.countDown();
156
        
157
        TestProjectOpenedHookImpl.toOpen.await();
158
159
        {
160
            TestSupport.TestProject p = null;
161
            for (int i = 0; i < 10; i++) {
162
                Node midNode2 = logicalView.getChildren().getNodes()[5];
163
                p = midNode.getLookup().lookup(TestSupport.TestProject.class);
164
                if (p != null) {
165
                    break;
166
                }
167
                Thread.sleep(100);
168
            }
169
            assertNotNull("The right project opened", p);
170
        }
171
        
172
        rest.countDown();
173
        rest.countDown();
174
        OpenProjectList.waitProjectsFullyOpen();
175
        
176
        assertEquals("All projects opened", 10, TestProjectOpenedHookImpl.opened);
177
        
178
179
        for (Node n : logicalView.getChildren().getNodes()) {
180
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
181
            assertNotNull("Nodes have correct project of this type", p);
182
        }
183
    }
184
    
185
    private static class L implements NodeListener {
186
        public List<EventObject> events = new ArrayList<EventObject>();
187
        
188
        public void childrenAdded(NodeMemberEvent ev) {
189
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
190
            events.add(ev);
191
        }
192
193
        public void childrenRemoved(NodeMemberEvent ev) {
194
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
195
            events.add(ev);
196
        }
197
198
        public void childrenReordered(NodeReorderEvent ev) {
199
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
200
            events.add(ev);
201
        }
202
203
        public void nodeDestroyed(NodeEvent ev) {
204
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
205
            events.add(ev);
206
        }
207
208
        public void propertyChange(PropertyChangeEvent evt) {
209
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
210
            events.add(evt);
211
        }
212
213
        final void assertEvents(String string, int i) {
214
            assertEquals(string + events, i, events.size());
215
            events.clear();
216
        }
217
        
218
    }
219
    
220
    private static class TestProjectOpenedHookImpl extends ProjectOpenedHook {
221
        
222
        public static CountDownLatch toOpen = new CountDownLatch(2);
223
        public static int opened = 0;
224
        public static int closed = 0;
225
        
226
        
227
        private CountDownLatch toWaitOn;
228
        
229
        public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) {
230
            this.toWaitOn = toWaitOn;
231
        }
232
        
233
        protected void projectClosed() {
234
            closed++;
235
        }
236
        
237
        protected void projectOpened() {
238
            if (toWaitOn != null) {
239
                try {
240
                    toWaitOn.await();
241
                } catch (InterruptedException ex) {
242
                    throw new IllegalStateException(ex);
243
                }
244
            }
245
            opened++;
246
            toOpen.countDown();
247
        }
248
        
249
    }
250
    
251
    public static final class ContextProv implements ContextGlobalProvider, 
252
    Lookup.Provider {
253
        private static Lookup current = Lookup.EMPTY;
254
        private static Lookup proxy;
255
        public ContextProv() {
256
            assert proxy == null;
257
            proxy = Lookups.proxy(this);
258
        }
259
        
260
        public static void assign(Lookup current) {
261
            ContextProv.current = current;
262
            // refresh
263
            if (proxy != null) {
264
                proxy.lookup((Class<?>)null);
265
            }
266
        }
267
268
        public Lookup createGlobalContext() {
269
            return proxy;
270
        }
271
272
        public Lookup getLookup() {
273
            return current;
274
        }
275
        
276
    }
277
}
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodePreferredOpenTest.java (+245 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 * 
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 * 
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 * 
35
 * Contributor(s):
36
 * 
37
 * Portions Copyrighted 2007 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.modules.project.ui;
41
42
import java.awt.EventQueue;
43
import java.beans.PropertyChangeEvent;
44
import java.net.URL;
45
import java.util.ArrayList;
46
import java.util.EventObject;
47
import java.util.List;
48
import java.util.concurrent.CountDownLatch;
49
import javax.swing.Action;
50
import junit.framework.TestCase;
51
import org.netbeans.api.project.Project;
52
import org.netbeans.api.project.ProjectManager;
53
import org.netbeans.junit.MockServices;
54
import org.netbeans.junit.NbTestCase;
55
import org.netbeans.modules.project.ui.actions.TestSupport;
56
import org.netbeans.spi.project.ui.ProjectOpenedHook;
57
import org.openide.filesystems.FileObject;
58
import org.openide.filesystems.FileUtil;
59
import org.openide.filesystems.URLMapper;
60
import org.openide.nodes.Node;
61
import org.openide.nodes.Node.Handle;
62
import org.openide.nodes.NodeEvent;
63
import org.openide.nodes.NodeListener;
64
import org.openide.nodes.NodeMemberEvent;
65
import org.openide.nodes.NodeReorderEvent;
66
import org.openide.util.Exceptions;
67
import org.openide.util.lookup.Lookups;
68
69
/** 
70
 *
71
 * @author Jaroslav Tulach <jtulach@netbeans.org>
72
 */
73
public class ProjectsRootNodePreferredOpenTest extends NbTestCase {
74
    CountDownLatch first;
75
    CountDownLatch middle;
76
    CountDownLatch rest;
77
    
78
    public ProjectsRootNodePreferredOpenTest(String testName) {
79
        super(testName);
80
    }            
81
82
    @Override
83
    protected void setUp() throws Exception {
84
        clearWorkDir();
85
        
86
        MockServices.setServices(TestSupport.TestProjectFactory.class);
87
        
88
        FileObject workDir = FileUtil.toFileObject(getWorkDir());
89
        assertNotNull(workDir);
90
        
91
        first = new CountDownLatch(1);
92
        middle = new CountDownLatch(1);
93
        rest = new CountDownLatch(2);
94
        
95
        List<URL> list = new ArrayList<URL>();
96
        List<ExtIcon> icons = new ArrayList<ExtIcon>();
97
        List<String> names = new ArrayList<String>();
98
        for (int i = 0; i < 10; i++) {
99
            FileObject prj = TestSupport.createTestProject(workDir, "prj" + i);
100
            URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL);
101
            list.add(url);
102
            names.add(url.toExternalForm());
103
            icons.add(new ExtIcon());
104
            TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj);
105
            assertNotNull("Project found", tmp);
106
            CountDownLatch down = i == 0 ? first : (i == 5 ? middle : rest);
107
            tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down)));
108
        }
109
        
110
        OpenProjectListSettings.getInstance().setOpenProjectsURLs(list);
111
        OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names);
112
        OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons);
113
    }
114
115
    @Override
116
    protected void tearDown() throws Exception {
117
        super.tearDown();
118
    }
119
120
    public void testPreferencesInOpenCanBeChanged() throws InterruptedException {
121
        Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW);
122
        L listener = new L();
123
        logicalView.addNodeListener(listener);
124
        
125
        assertEquals("10 children", 10, logicalView.getChildren().getNodesCount());
126
        listener.assertEvents("None", 0);
127
        assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened);
128
        
129
        for (Node n : logicalView.getChildren().getNodes()) {
130
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
131
            assertNull("No project of this type, yet", p);
132
        }
133
        
134
        Node midNode = logicalView.getChildren().getNodes()[5];
135
        {
136
            TestSupport.TestProject p = midNode.getLookup().lookup(TestSupport.TestProject.class);
137
            assertNull("No project of this type, yet", p);
138
        }
139
        Project lazyP = midNode.getLookup().lookup(Project.class);
140
        assertNotNull("Some project is found", lazyP);
141
        assertEquals("It is lazy project", LazyProject.class, lazyP.getClass());
142
        
143
        middle.countDown();
144
        // not necessary, but to ensure middle really does not run
145
        Thread.sleep(300);
146
        assertEquals("Still no processing", 0, TestProjectOpenedHookImpl.opened);
147
        // trigger initialization of the node, shall trigger OpenProjectList.preferredProject(lazyP);
148
        midNode.getChildren().getNodes();
149
        first.countDown();
150
        
151
        TestProjectOpenedHookImpl.toOpen.await();
152
153
        {
154
            TestSupport.TestProject p = null;
155
            for (int i = 0; i < 10; i++) {
156
                Node midNode2 = logicalView.getChildren().getNodes()[5];
157
                p = midNode.getLookup().lookup(TestSupport.TestProject.class);
158
                if (p != null) {
159
                    break;
160
                }
161
                Thread.sleep(100);
162
            }
163
            assertNotNull("The right project opened", p);
164
        }
165
        
166
        rest.countDown();
167
        rest.countDown();
168
        
169
        OpenProjectList.waitProjectsFullyOpen();
170
        
171
        assertEquals("All projects opened", 10, TestProjectOpenedHookImpl.opened);
172
        
173
174
        for (Node n : logicalView.getChildren().getNodes()) {
175
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
176
            assertNotNull("Nodes have correct project of this type", p);
177
        }
178
    }
179
    
180
    private static class L implements NodeListener {
181
        public List<EventObject> events = new ArrayList<EventObject>();
182
        
183
        public void childrenAdded(NodeMemberEvent ev) {
184
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
185
            events.add(ev);
186
        }
187
188
        public void childrenRemoved(NodeMemberEvent ev) {
189
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
190
            events.add(ev);
191
        }
192
193
        public void childrenReordered(NodeReorderEvent ev) {
194
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
195
            events.add(ev);
196
        }
197
198
        public void nodeDestroyed(NodeEvent ev) {
199
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
200
            events.add(ev);
201
        }
202
203
        public void propertyChange(PropertyChangeEvent evt) {
204
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
205
            events.add(evt);
206
        }
207
208
        final void assertEvents(String string, int i) {
209
            assertEquals(string + events, i, events.size());
210
            events.clear();
211
        }
212
        
213
    }
214
    
215
    private static class TestProjectOpenedHookImpl extends ProjectOpenedHook {
216
        
217
        public static CountDownLatch toOpen = new CountDownLatch(2);
218
        public static int opened = 0;
219
        public static int closed = 0;
220
        
221
        
222
        private CountDownLatch toWaitOn;
223
        
224
        public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) {
225
            this.toWaitOn = toWaitOn;
226
        }
227
        
228
        protected void projectClosed() {
229
            closed++;
230
        }
231
        
232
        protected void projectOpened() {
233
            if (toWaitOn != null) {
234
                try {
235
                    toWaitOn.await();
236
                } catch (InterruptedException ex) {
237
                    throw new IllegalStateException(ex);
238
                }
239
            }
240
            opened++;
241
            toOpen.countDown();
242
        }
243
        
244
    }
245
}
(-)projects/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectsRootNodeTest.java (+209 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5
 * 
6
 * The contents of this file are subject to the terms of either the GNU
7
 * General Public License Version 2 only ("GPL") or the Common
8
 * Development and Distribution License("CDDL") (collectively, the
9
 * "License"). You may not use this file except in compliance with the
10
 * License. You can obtain a copy of the License at
11
 * http://www.netbeans.org/cddl-gplv2.html
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
 * specific language governing permissions and limitations under the
14
 * License.  When distributing the software, include this License Header
15
 * Notice in each file and include the License file at
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
17
 * particular file as subject to the "Classpath" exception as provided
18
 * by Sun in the GPL Version 2 section of the License file that
19
 * accompanied this code. If applicable, add the following below the
20
 * License Header, with the fields enclosed by brackets [] replaced by
21
 * your own identifying information:
22
 * "Portions Copyrighted [year] [name of copyright owner]"
23
 * 
24
 * If you wish your version of this file to be governed by only the CDDL
25
 * or only the GPL Version 2, indicate your decision by adding
26
 * "[Contributor] elects to include this software in this distribution
27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
28
 * single choice of license, a recipient has the option to distribute
29
 * your version of this file under either the CDDL, the GPL Version 2 or
30
 * to extend the choice of license to its licensees as provided above.
31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
32
 * Version 2 license, then the option applies only if the new code is
33
 * made subject to such option by the copyright holder.
34
 * 
35
 * Contributor(s):
36
 * 
37
 * Portions Copyrighted 2007 Sun Microsystems, Inc.
38
 */
39
40
package org.netbeans.modules.project.ui;
41
42
import java.awt.EventQueue;
43
import java.beans.PropertyChangeEvent;
44
import java.net.URL;
45
import java.util.ArrayList;
46
import java.util.EventObject;
47
import java.util.List;
48
import java.util.concurrent.CountDownLatch;
49
import javax.swing.Action;
50
import junit.framework.TestCase;
51
import org.netbeans.api.project.ProjectManager;
52
import org.netbeans.junit.MockServices;
53
import org.netbeans.junit.NbTestCase;
54
import org.netbeans.modules.project.ui.actions.TestSupport;
55
import org.netbeans.spi.project.ui.ProjectOpenedHook;
56
import org.openide.filesystems.FileObject;
57
import org.openide.filesystems.FileUtil;
58
import org.openide.filesystems.URLMapper;
59
import org.openide.nodes.Node;
60
import org.openide.nodes.Node.Handle;
61
import org.openide.nodes.NodeEvent;
62
import org.openide.nodes.NodeListener;
63
import org.openide.nodes.NodeMemberEvent;
64
import org.openide.nodes.NodeReorderEvent;
65
import org.openide.util.Exceptions;
66
import org.openide.util.lookup.Lookups;
67
68
/** 
69
 *
70
 * @author Jaroslav Tulach <jtulach@netbeans.org>
71
 */
72
public class ProjectsRootNodeTest extends NbTestCase {
73
    CountDownLatch down;
74
    
75
    public ProjectsRootNodeTest(String testName) {
76
        super(testName);
77
    }            
78
79
    @Override
80
    protected void setUp() throws Exception {
81
        clearWorkDir();
82
        
83
        MockServices.setServices(TestSupport.TestProjectFactory.class);
84
        
85
        FileObject workDir = FileUtil.toFileObject(getWorkDir());
86
        assertNotNull(workDir);
87
        
88
        down = new CountDownLatch(1);
89
        
90
        List<URL> list = new ArrayList<URL>();
91
        List<ExtIcon> icons = new ArrayList<ExtIcon>();
92
        List<String> names = new ArrayList<String>();
93
        for (int i = 0; i < 30; i++) {
94
            FileObject prj = TestSupport.createTestProject(workDir, "prj" + i);
95
            URL url = URLMapper.findURL(prj, URLMapper.EXTERNAL);
96
            list.add(url);
97
            names.add(url.toExternalForm());
98
            icons.add(new ExtIcon());
99
            TestSupport.TestProject tmp = (TestSupport.TestProject)ProjectManager.getDefault ().findProject (prj);
100
            assertNotNull("Project found", tmp);
101
            tmp.setLookup(Lookups.singleton(new TestProjectOpenedHookImpl(down)));
102
        }
103
        
104
        OpenProjectListSettings.getInstance().setOpenProjectsURLs(list);
105
        OpenProjectListSettings.getInstance().setOpenProjectsDisplayNames(names);
106
        OpenProjectListSettings.getInstance().setOpenProjectsIcons(icons);
107
    }
108
109
    @Override
110
    protected void tearDown() throws Exception {
111
        super.tearDown();
112
    }
113
114
    public void testBehaviourOfProjectsLogicNode() throws InterruptedException {
115
        Node logicalView = new ProjectsRootNode(ProjectsRootNode.LOGICAL_VIEW);
116
        L listener = new L();
117
        logicalView.addNodeListener(listener);
118
        
119
        assertEquals("30 children", 30, logicalView.getChildren().getNodesCount());
120
        listener.assertEvents("None", 0);
121
        assertEquals("No project opened yet", 0, TestProjectOpenedHookImpl.opened);
122
        
123
        for (Node n : logicalView.getChildren().getNodes()) {
124
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
125
            assertNull("No project of this type, yet", p);
126
        }
127
        
128
        // let project open code run
129
        down.countDown();
130
        TestProjectOpenedHookImpl.toOpen.await();
131
        
132
        assertEquals("All projects opened", 30, TestProjectOpenedHookImpl.opened);
133
        
134
        OpenProjectList.waitProjectsFullyOpen();
135
136
        for (Node n : logicalView.getChildren().getNodes()) {
137
            TestSupport.TestProject p = n.getLookup().lookup(TestSupport.TestProject.class);
138
            assertNotNull("Nodes have correct project of this type", p);
139
        }
140
        
141
        listener.assertEvents("Goal is to receive no events at all", 0);
142
    }
143
    
144
    private static class L implements NodeListener {
145
        public List<EventObject> events = new ArrayList<EventObject>();
146
        
147
        public void childrenAdded(NodeMemberEvent ev) {
148
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
149
            events.add(ev);
150
        }
151
152
        public void childrenRemoved(NodeMemberEvent ev) {
153
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
154
            events.add(ev);
155
        }
156
157
        public void childrenReordered(NodeReorderEvent ev) {
158
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
159
            events.add(ev);
160
        }
161
162
        public void nodeDestroyed(NodeEvent ev) {
163
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
164
            events.add(ev);
165
        }
166
167
        public void propertyChange(PropertyChangeEvent evt) {
168
            assertFalse("No event in AWT thread", EventQueue.isDispatchThread());
169
            events.add(evt);
170
        }
171
172
        final void assertEvents(String string, int i) {
173
            assertEquals(string + events, i, events.size());
174
            events.clear();
175
        }
176
        
177
    }
178
    
179
    private static class TestProjectOpenedHookImpl extends ProjectOpenedHook {
180
        
181
        public static CountDownLatch toOpen = new CountDownLatch(30);
182
        public static int opened = 0;
183
        public static int closed = 0;
184
        
185
        
186
        private CountDownLatch toWaitOn;
187
        
188
        public TestProjectOpenedHookImpl(CountDownLatch toWaitOn) {
189
            this.toWaitOn = toWaitOn;
190
        }
191
        
192
        protected void projectClosed() {
193
            closed++;
194
        }
195
        
196
        protected void projectOpened() {
197
            if (toWaitOn != null) {
198
                try {
199
                    toWaitOn.await();
200
                } catch (InterruptedException ex) {
201
                    throw new IllegalStateException(ex);
202
                }
203
            }
204
            opened++;
205
            toOpen.countDown();
206
        }
207
        
208
    }
209
}

Return to bug 108120