0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.project.ui;
0043:
0044: import java.awt.Dimension;
0045: import java.awt.EventQueue;
0046: import java.awt.Frame;
0047: import java.awt.Rectangle;
0048: import java.beans.PropertyChangeEvent;
0049: import java.beans.PropertyChangeListener;
0050: import java.beans.PropertyChangeSupport;
0051: import java.io.File;
0052: import java.io.IOException;
0053: import java.lang.ref.WeakReference;
0054: import java.net.URISyntaxException;
0055: import java.net.URL;
0056: import java.text.Collator;
0057: import java.util.ArrayList;
0058: import java.util.Arrays;
0059: import java.util.Collection;
0060: import java.util.Collections;
0061: import java.util.Comparator;
0062: import java.util.HashMap;
0063: import java.util.Iterator;
0064: import java.util.LinkedHashSet;
0065: import java.util.LinkedList;
0066: import java.util.List;
0067: import java.util.Map;
0068: import java.util.Set;
0069: import java.util.StringTokenizer;
0070: import java.util.concurrent.CopyOnWriteArrayList;
0071: import java.util.concurrent.ExecutionException;
0072: import java.util.concurrent.Future;
0073: import java.util.concurrent.TimeUnit;
0074: import java.util.concurrent.TimeoutException;
0075: import java.util.concurrent.locks.Condition;
0076: import java.util.concurrent.locks.Lock;
0077: import java.util.concurrent.locks.ReentrantLock;
0078: import java.util.logging.Level;
0079: import java.util.logging.LogRecord;
0080: import java.util.logging.Logger;
0081: import javax.swing.Icon;
0082: import javax.swing.JDialog;
0083: import javax.swing.SwingUtilities;
0084: import org.netbeans.api.progress.ProgressHandle;
0085: import org.netbeans.api.progress.ProgressHandleFactory;
0086: import org.netbeans.api.project.FileOwnerQuery;
0087: import org.netbeans.api.project.Project;
0088: import org.netbeans.api.project.ProjectInformation;
0089: import org.netbeans.api.project.ProjectManager;
0090: import org.netbeans.api.project.ProjectUtils;
0091: import org.netbeans.modules.project.ui.api.UnloadedProjectInformation;
0092: import org.netbeans.modules.project.uiapi.ProjectOpenedTrampoline;
0093: import org.netbeans.spi.project.SubprojectProvider;
0094: import org.netbeans.spi.project.ui.PrivilegedTemplates;
0095: import org.netbeans.spi.project.ui.ProjectOpenedHook;
0096: import org.netbeans.spi.project.ui.RecommendedTemplates;
0097: import org.openide.ErrorManager;
0098: import org.openide.filesystems.FileChangeAdapter;
0099: import org.openide.filesystems.FileEvent;
0100: import org.openide.filesystems.FileObject;
0101: import org.openide.filesystems.FileStateInvalidException;
0102: import org.openide.filesystems.FileSystem;
0103: import org.openide.filesystems.FileUtil;
0104: import org.openide.filesystems.Repository;
0105: import org.openide.filesystems.URLMapper;
0106: import org.openide.loaders.DataObject;
0107: import org.openide.loaders.DataObjectNotFoundException;
0108: import org.openide.modules.ModuleInfo;
0109: import org.openide.util.Lookup;
0110: import org.openide.util.LookupEvent;
0111: import org.openide.util.LookupListener;
0112: import org.openide.util.Mutex;
0113: import org.openide.util.Mutex.Action;
0114: import org.openide.util.NbBundle;
0115: import org.openide.util.RequestProcessor;
0116: import org.openide.util.Utilities;
0117: import org.openide.util.WeakListeners;
0118: import org.openide.windows.WindowManager;
0119:
0120: /**
0121: * List of projects open in the GUI.
0122: * @author Petr Hrebejk
0123: */
0124: public final class OpenProjectList {
0125:
0126: public static final Comparator<Project> PROJECT_BY_DISPLAYNAME = new ProjectByDisplayNameComparator();
0127:
0128: // Property names
0129: public static final String PROPERTY_OPEN_PROJECTS = "OpenProjects";
0130: public static final String PROPERTY_MAIN_PROJECT = "MainProject";
0131: public static final String PROPERTY_RECENT_PROJECTS = "RecentProjects";
0132: public static final String PROPERTY_REPLACE = "ReplaceProject";
0133:
0134: private static OpenProjectList INSTANCE;
0135:
0136: // number of templates in LRU list
0137: private static final int NUM_TEMPLATES = 15;
0138:
0139: static final Logger LOGGER = Logger.getLogger(OpenProjectList.class
0140: .getName());
0141: private static final Level LOG_LEVEL = Level.FINE;
0142:
0143: private static final RequestProcessor OPENING_RP = new RequestProcessor(
0144: "Opening projects", 1);
0145:
0146: /** List which holds the open projects */
0147: private List<Project> openProjects;
0148: private HashMap<ModuleInfo, List<Project>> openProjectsModuleInfos;
0149:
0150: /** Main project */
0151: private Project mainProject;
0152:
0153: /** List of recently closed projects */
0154: private final RecentProjectList recentProjects;
0155:
0156: /** lock to prevent modifications of the recentTemplates variable from multiple threads */
0157: private static Object RECENT_TEMPLATES_LOCK = new Object();
0158: /** LRU List of recently used templates */
0159: private final List<String> recentTemplates;
0160:
0161: /** Property change listeners */
0162: private final PropertyChangeSupport pchSupport;
0163:
0164: private ProjectDeletionListener deleteListener = new ProjectDeletionListener();
0165: private NbProjectDeletionListener nbprojectDeleteListener = new NbProjectDeletionListener();
0166:
0167: private PropertyChangeListener infoListener;
0168: private final LoadOpenProjects LOAD;
0169:
0170: OpenProjectList() {
0171: LOAD = new LoadOpenProjects(0);
0172: openProjects = new ArrayList<Project>();
0173: openProjectsModuleInfos = new HashMap<ModuleInfo, List<Project>>();
0174: infoListener = new PropertyChangeListener() {
0175: public void propertyChange(PropertyChangeEvent evn) {
0176: if (ModuleInfo.PROP_ENABLED.equals(evn
0177: .getPropertyName())) {
0178: checkModuleInfo((ModuleInfo) evn.getSource());
0179: }
0180: }
0181: };
0182: pchSupport = new PropertyChangeSupport(this );
0183: recentProjects = new RecentProjectList(10); // #47134
0184: recentTemplates = new ArrayList<String>();
0185: }
0186:
0187: // Implementation of the class ---------------------------------------------
0188:
0189: public static OpenProjectList getDefault() {
0190: boolean needNotify = false;
0191:
0192: Project[] inital = null;
0193: synchronized (OpenProjectList.class) {
0194: if (INSTANCE == null) {
0195: INSTANCE = new OpenProjectList();
0196: INSTANCE.openProjects = loadProjectList();
0197: WindowManager.getDefault().invokeWhenUIReady(
0198: INSTANCE.LOAD);
0199: }
0200: }
0201: return INSTANCE;
0202: }
0203:
0204: static void waitProjectsFullyOpen() {
0205: getDefault().LOAD.waitFinished();
0206: }
0207:
0208: static void preferredProject(Project lazyP) {
0209: if (lazyP != null) {
0210: getDefault().LOAD.preferredProject(lazyP);
0211: }
0212: }
0213:
0214: Future<Project[]> openProjectsAPI() {
0215: return LOAD;
0216: }
0217:
0218: /** Modifications to the recentTemplates variables shall be done only
0219: * when hodling a lock.
0220: * @return the list
0221: */
0222: private List<String> getRecentTemplates() {
0223: assert Thread.holdsLock(this );
0224: return recentTemplates;
0225: }
0226:
0227: private final class LoadOpenProjects implements Runnable,
0228: LookupListener, Future<Project[]> {
0229: final RequestProcessor RP = new RequestProcessor(
0230: "Load Open Projects"); // NOI18N
0231: final RequestProcessor.Task TASK = RP.create(this );
0232: private int action;
0233: private LinkedList<Project> toOpenProjects = new LinkedList<Project>();
0234: private List<Project> openedProjects;
0235: private List<String> recentTemplates;
0236: private Project mainProject;
0237: private Lookup.Result<FileObject> currentFiles;
0238: private int entered;
0239: private final Lock enteredGuard = new ReentrantLock();
0240: private final Condition enteredZeroed = enteredGuard
0241: .newCondition();
0242:
0243: public LoadOpenProjects(int a) {
0244: action = a;
0245: currentFiles = Utilities.actionsGlobalContext()
0246: .lookupResult(FileObject.class);
0247: currentFiles.addLookupListener(WeakListeners.create(
0248: LookupListener.class, this , currentFiles));
0249: }
0250:
0251: final void waitFinished() {
0252: if (action == 0) {
0253: run();
0254: }
0255: TASK.waitFinished();
0256: }
0257:
0258: public void run() {
0259: switch (action) {
0260: case 0:
0261: action = 1;
0262: TASK.schedule(0);
0263: resultChanged(null);
0264: return;
0265: case 1:
0266: action = 2;
0267: loadOnBackground();
0268: updateGlobalState();
0269: return;
0270: case 2:
0271: // finished, oK
0272: return;
0273: default:
0274: throw new IllegalStateException("unknown action: "
0275: + action);
0276: }
0277: }
0278:
0279: final void preferredProject(Project lazyP) {
0280: synchronized (toOpenProjects) {
0281: for (Project p : toOpenProjects) {
0282: if (p.getProjectDirectory().equals(
0283: lazyP.getProjectDirectory())) {
0284: toOpenProjects.remove(p);
0285: toOpenProjects.addFirst(p);
0286: return;
0287: }
0288: }
0289: }
0290: }
0291:
0292: private void updateGlobalState() {
0293: synchronized (INSTANCE) {
0294: INSTANCE.openProjects = openedProjects;
0295: INSTANCE.mainProject = mainProject;
0296: INSTANCE.getRecentTemplates().addAll(recentTemplates);
0297: }
0298:
0299: INSTANCE.pchSupport.firePropertyChange(
0300: PROPERTY_OPEN_PROJECTS, new Project[0],
0301: openedProjects.toArray(new Project[0]));
0302: INSTANCE.pchSupport.firePropertyChange(
0303: PROPERTY_MAIN_PROJECT, null, INSTANCE.mainProject);
0304: }
0305:
0306: private void loadOnBackground() {
0307: openedProjects = new ArrayList<Project>();
0308: List<URL> URLs = OpenProjectListSettings.getInstance()
0309: .getOpenProjectsURLs();
0310: toOpenProjects.addAll(URLs2Projects(URLs));
0311: Project[] inital;
0312: synchronized (toOpenProjects) {
0313: inital = toOpenProjects.toArray(new Project[0]);
0314: }
0315: recentTemplates = new ArrayList<String>(
0316: OpenProjectListSettings.getInstance()
0317: .getRecentTemplates());
0318: URL mainProjectURL = OpenProjectListSettings.getInstance()
0319: .getMainProjectURL();
0320: // Load recent project list
0321: INSTANCE.recentProjects.load();
0322: synchronized (toOpenProjects) {
0323: for (Iterator it = toOpenProjects.iterator(); it
0324: .hasNext();) {
0325: Project p = (Project) it.next();
0326: INSTANCE.addModuleInfo(p);
0327: // Set main project
0328: try {
0329: if (mainProjectURL != null
0330: && mainProjectURL
0331: .equals(p.getProjectDirectory()
0332: .getURL())) {
0333: mainProject = p;
0334: }
0335: } catch (FileStateInvalidException e) {
0336: // Not a main project
0337: }
0338: }
0339: }
0340: for (;;) {
0341: Project p;
0342: synchronized (toOpenProjects) {
0343: if (toOpenProjects.isEmpty()) {
0344: break;
0345: }
0346: p = toOpenProjects.remove();
0347: }
0348: openedProjects.add(p);
0349: notifyOpened(p);
0350: PropertyChangeEvent ev = new PropertyChangeEvent(this ,
0351: PROPERTY_REPLACE, null, p);
0352: pchSupport.firePropertyChange(ev);
0353: }
0354:
0355: if (inital != null) {
0356: log(createRecord("UI_INIT_PROJECTS", inital));
0357: }
0358:
0359: }
0360:
0361: public void resultChanged(LookupEvent ev) {
0362: for (FileObject fileObject : currentFiles.allInstances()) {
0363: Project p = FileOwnerQuery.getOwner(fileObject);
0364: OpenProjectList.preferredProject(p);
0365: }
0366:
0367: }
0368:
0369: final void enter() {
0370: try {
0371: enteredGuard.lock();
0372: entered++;
0373: } finally {
0374: enteredGuard.unlock();
0375: }
0376: }
0377:
0378: final void exit() {
0379: try {
0380: enteredGuard.lock();
0381: if (--entered == 0) {
0382: enteredZeroed.signalAll();
0383: }
0384: } finally {
0385: enteredGuard.unlock();
0386: }
0387: }
0388:
0389: public boolean cancel(boolean mayInterruptIfRunning) {
0390: return false;
0391: }
0392:
0393: public boolean isCancelled() {
0394: return false;
0395: }
0396:
0397: public boolean isDone() {
0398: return TASK.isFinished() && entered == 0;
0399: }
0400:
0401: public Project[] get() throws InterruptedException,
0402: ExecutionException {
0403: TASK.waitFinished();
0404: try {
0405: enteredGuard.lock();
0406: while (entered > 0) {
0407: enteredZeroed.await();
0408: }
0409: } finally {
0410: enteredGuard.unlock();
0411: }
0412: return getDefault().getOpenProjects();
0413: }
0414:
0415: public Project[] get(long timeout, TimeUnit unit)
0416: throws InterruptedException, ExecutionException,
0417: TimeoutException {
0418: long ms = unit.convert(timeout, TimeUnit.MILLISECONDS);
0419: if (!TASK.waitFinished(timeout)) {
0420: throw new TimeoutException();
0421: }
0422: try {
0423: enteredGuard.lock();
0424: if (entered > 0) {
0425: if (!enteredZeroed.await(ms, TimeUnit.MILLISECONDS)) {
0426: throw new TimeoutException();
0427: }
0428: }
0429: } finally {
0430: enteredGuard.unlock();
0431: }
0432: return getDefault().getOpenProjects();
0433: }
0434: }
0435:
0436: public void open(Project p) {
0437: open(new Project[] { p }, false);
0438: }
0439:
0440: public void open(Project p, boolean openSubprojects) {
0441: open(new Project[] { p }, openSubprojects);
0442: }
0443:
0444: public void open(Project[] projects, boolean openSubprojects) {
0445: open(projects, openSubprojects, false);
0446: }
0447:
0448: public void open(final Project[] projects,
0449: final boolean openSubprojects, final boolean asynchronously) {
0450: if (projects.length == 0) {
0451: //nothing to do:
0452: return;
0453: }
0454:
0455: long start = System.currentTimeMillis();
0456:
0457: if (asynchronously) {
0458: if (!EventQueue.isDispatchThread()) { // #89935
0459: EventQueue.invokeLater(new Runnable() {
0460: public void run() {
0461: open(projects, openSubprojects, asynchronously);
0462: }
0463: });
0464: return;
0465: }
0466: final ProgressHandle handle = ProgressHandleFactory
0467: .createHandle(NbBundle.getMessage(
0468: OpenProjectList.class,
0469: "CAP_Opening_Projects"));
0470: final Frame mainWindow = WindowManager.getDefault()
0471: .getMainWindow();
0472: final JDialog dialog = new JDialog(mainWindow, NbBundle
0473: .getMessage(OpenProjectList.class,
0474: "LBL_Opening_Projects_Progress"), true);
0475: final OpeningProjectPanel panel = new OpeningProjectPanel(
0476: handle);
0477:
0478: dialog.getContentPane().add(panel);
0479: dialog
0480: .setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); //make sure the dialog is not closed during the project open
0481: dialog.pack();
0482:
0483: Rectangle bounds = mainWindow.getBounds();
0484:
0485: int middleX = bounds.x + bounds.width / 2;
0486: int middleY = bounds.y + bounds.height / 2;
0487:
0488: Dimension size = dialog.getPreferredSize();
0489:
0490: dialog.setBounds(middleX - size.width / 2, middleY
0491: - size.height / 2, size.width, size.height);
0492:
0493: OPENING_RP.post(new Runnable() {
0494: public void run() {
0495: try {
0496: doOpen(projects, openSubprojects, handle, panel);
0497: } finally {
0498: SwingUtilities.invokeLater(new Runnable() {
0499: public void run() {
0500: //fix for #67114:
0501: try {
0502: Thread.sleep(50);
0503: } catch (InterruptedException e) {
0504: // ignored
0505: }
0506: dialog.setVisible(false);
0507: dialog.dispose();
0508: }
0509: });
0510: }
0511: }
0512: });
0513:
0514: dialog.setVisible(true);
0515: } else {
0516: doOpen(projects, openSubprojects, null, null);
0517: }
0518:
0519: long end = System.currentTimeMillis();
0520:
0521: if (LOGGER.isLoggable(LOG_LEVEL)) {
0522: LOGGER.log(LOG_LEVEL, "opening projects took: "
0523: + (end - start) + "ms");
0524: }
0525: }
0526:
0527: private void doOpen(Project[] projects, boolean openSubprojects,
0528: ProgressHandle handle, OpeningProjectPanel panel) {
0529: assert !Arrays.asList(projects).contains(null) : "Projects can't be null";
0530: LOAD.waitFinished();
0531:
0532: try {
0533: LOAD.enter();
0534: boolean recentProjectsChanged = false;
0535: int maxWork = 1000;
0536: int workForSubprojects = maxWork / 2;
0537: double currentWork = 0;
0538: Collection<Project> projectsToOpen = new LinkedHashSet<Project>();
0539:
0540: if (handle != null) {
0541: handle.start(maxWork);
0542: handle.progress(0);
0543: }
0544:
0545: if (panel != null) {
0546: assert projects.length > 0 : "at least one project to open";
0547:
0548: panel.setProjectName(ProjectUtils.getInformation(
0549: projects[0]).getDisplayName());
0550: }
0551:
0552: Map<Project, Set<? extends Project>> subprojectsCache = new HashMap<Project, Set<? extends Project>>(); // #59098
0553:
0554: List<Project> toHandle = new LinkedList<Project>(Arrays
0555: .asList(projects));
0556:
0557: while (!toHandle.isEmpty()) {
0558: Project p = toHandle.remove(0);
0559: Set<? extends Project> subprojects = openSubprojects ? subprojectsCache
0560: .get(p)
0561: : Collections.<Project> emptySet();
0562:
0563: if (subprojects == null) {
0564: SubprojectProvider spp = p.getLookup().lookup(
0565: SubprojectProvider.class);
0566: if (spp != null) {
0567: subprojects = spp.getSubprojects();
0568: } else {
0569: subprojects = Collections.emptySet();
0570: }
0571: subprojectsCache.put(p, subprojects);
0572: }
0573:
0574: projectsToOpen.add(p);
0575:
0576: for (Project sub : subprojects) {
0577: if (!projectsToOpen.contains(sub)
0578: && !toHandle.contains(sub)) {
0579: toHandle.add(sub);
0580: }
0581: }
0582:
0583: double workPerOneProject = (workForSubprojects - currentWork)
0584: / (toHandle.size() + 1);
0585: int lastState = (int) currentWork;
0586:
0587: currentWork += workPerOneProject;
0588:
0589: if (handle != null && lastState < (int) currentWork) {
0590: handle.progress((int) currentWork);
0591: }
0592: }
0593:
0594: double workPerProject = (maxWork - workForSubprojects)
0595: / projectsToOpen.size();
0596:
0597: final List<Project> oldprjs = new ArrayList<Project>();
0598: final List<Project> newprjs = new ArrayList<Project>();
0599: synchronized (this ) {
0600: oldprjs.addAll(openProjects);
0601: }
0602:
0603: for (Project p : projectsToOpen) {
0604:
0605: if (panel != null) {
0606: panel.setProjectName(ProjectUtils.getInformation(p)
0607: .getDisplayName());
0608: }
0609:
0610: recentProjectsChanged |= doOpenProject(p);
0611:
0612: int lastState = (int) currentWork;
0613:
0614: currentWork += workPerProject;
0615:
0616: if (handle != null && lastState < (int) currentWork) {
0617: handle.progress((int) currentWork);
0618: }
0619: }
0620:
0621: synchronized (this ) {
0622: newprjs.addAll(openProjects);
0623: saveProjectList(openProjects);
0624: if (recentProjectsChanged) {
0625: recentProjects.save();
0626: }
0627: }
0628:
0629: if (handle != null) {
0630: handle.finish();
0631: }
0632:
0633: final boolean recentProjectsChangedCopy = recentProjectsChanged;
0634:
0635: LogRecord[] addedRec = createRecord("UI_OPEN_PROJECTS",
0636: projectsToOpen.toArray(new Project[0])); // NOI18N
0637: log(addedRec);
0638:
0639: Mutex.EVENT.readAccess(new Action<Void>() {
0640: public Void run() {
0641: pchSupport
0642: .firePropertyChange(PROPERTY_OPEN_PROJECTS,
0643: oldprjs.toArray(new Project[oldprjs
0644: .size()]),
0645: newprjs.toArray(new Project[newprjs
0646: .size()]));
0647: if (recentProjectsChangedCopy) {
0648: pchSupport.firePropertyChange(
0649: PROPERTY_RECENT_PROJECTS, null, null);
0650: }
0651:
0652: return null;
0653: }
0654: });
0655: } finally {
0656: LOAD.exit();
0657: }
0658: }
0659:
0660: public void close(Project someProjects[], boolean notifyUI) {
0661: LOAD.waitFinished();
0662:
0663: Project[] projects = new Project[someProjects.length];
0664: Project[] now = getOpenProjects();
0665: for (int i = 0; i < someProjects.length; i++) {
0666: projects[i] = someProjects[i];
0667: if (someProjects[i] instanceof LazyProject) {
0668: LazyProject lp = (LazyProject) someProjects[i];
0669: for (Project p : now) {
0670: if (lp.getProjectDirectory().equals(
0671: p.getProjectDirectory())) {
0672: projects[i] = p;
0673: break;
0674: }
0675: }
0676: }
0677: }
0678:
0679: if (!ProjectUtilities.closeAllDocuments(projects, notifyUI)) {
0680: return;
0681: }
0682:
0683: try {
0684: LOAD.enter();
0685:
0686: logProjects("close(): closing project: ", projects);
0687: boolean mainClosed = false;
0688: boolean someClosed = false;
0689: List<Project> oldprjs = new ArrayList<Project>();
0690: List<Project> newprjs = new ArrayList<Project>();
0691: List<Project> notifyList = new ArrayList<Project>();
0692: synchronized (this ) {
0693: oldprjs.addAll(openProjects);
0694: for (int i = 0; i < projects.length; i++) {
0695: if (!openProjects.contains(projects[i])) {
0696: continue; // Nothing to remove
0697: }
0698: if (!mainClosed) {
0699: mainClosed = isMainProject(projects[i]);
0700: }
0701: openProjects.remove(projects[i]);
0702: removeModuleInfo(projects[i]);
0703:
0704: projects[i].getProjectDirectory()
0705: .removeFileChangeListener(deleteListener);
0706:
0707: recentProjects.add(projects[i]);
0708: notifyList.add(projects[i]);
0709:
0710: someClosed = true;
0711: }
0712: if (someClosed) {
0713: newprjs.addAll(openProjects);
0714: saveProjectList(openProjects);
0715: }
0716: if (mainClosed) {
0717: this .mainProject = null;
0718: saveMainProject(mainProject);
0719: }
0720: if (someClosed) {
0721: recentProjects.save();
0722: }
0723: }
0724: //#125750 not necessary to call notifyClosed() under synchronized lock.
0725: for (Project closed : notifyList) {
0726: notifyClosed(closed);
0727: }
0728: logProjects("close(): openProjects == ", openProjects
0729: .toArray(new Project[0])); // NOI18N
0730: if (someClosed) {
0731: pchSupport.firePropertyChange(PROPERTY_OPEN_PROJECTS,
0732: oldprjs.toArray(new Project[oldprjs.size()]),
0733: newprjs.toArray(new Project[newprjs.size()]));
0734: }
0735: if (mainClosed) {
0736: pchSupport.firePropertyChange(PROPERTY_MAIN_PROJECT,
0737: null, null);
0738: }
0739: if (someClosed) {
0740: pchSupport.firePropertyChange(PROPERTY_RECENT_PROJECTS,
0741: null, null);
0742: }
0743: // Noticed in #72006: save them, in case e.g. editor stored bookmarks when receiving PROPERTY_OPEN_PROJECTS.
0744: for (int i = 0; i < projects.length; i++) {
0745: try {
0746: ProjectManager.getDefault()
0747: .saveProject(projects[i]);
0748: } catch (IOException e) {
0749: ErrorManager.getDefault().notify(
0750: ErrorManager.INFORMATIONAL, e);
0751: }
0752: }
0753: LogRecord[] removedRec = createRecord("UI_CLOSED_PROJECTS",
0754: projects); // NOI18N
0755: log(removedRec);
0756: } finally {
0757: LOAD.exit();
0758: }
0759: }
0760:
0761: public synchronized Project[] getOpenProjects() {
0762: Project projects[] = new Project[openProjects.size()];
0763: openProjects.toArray(projects);
0764: return projects;
0765: }
0766:
0767: public synchronized boolean isOpen(Project p) {
0768: // XXX shouldn't this just use openProjects.contains(p)?
0769: for (Iterator it = openProjects.iterator(); it.hasNext();) {
0770: Project cp = (Project) it.next();
0771: if (p.getProjectDirectory()
0772: .equals(cp.getProjectDirectory())) {
0773: return true;
0774: }
0775: }
0776: return false;
0777: }
0778:
0779: public synchronized boolean isMainProject(Project p) {
0780:
0781: if (mainProject != null
0782: && p != null
0783: && mainProject.getProjectDirectory().equals(
0784: p.getProjectDirectory())) {
0785: return true;
0786: } else {
0787: return false;
0788: }
0789:
0790: }
0791:
0792: public synchronized Project getMainProject() {
0793: return mainProject;
0794: }
0795:
0796: public void setMainProject(Project mainProject) {
0797: LOGGER.finer("Setting main project: " + mainProject); // NOI18N
0798: logProjects("setMainProject(): openProjects == ", openProjects
0799: .toArray(new Project[0])); // NOI18N
0800: synchronized (this ) {
0801: if (mainProject != null
0802: && !openProjects.contains(mainProject)) {
0803: logProjects("setMainProject(): openProjects == ",
0804: openProjects.toArray(new Project[0])); // NOI18N
0805: throw new IllegalArgumentException("Project "
0806: + ProjectUtils.getInformation(mainProject)
0807: .getDisplayName()
0808: + " is not open and cannot be set as main.");
0809: }
0810:
0811: this .mainProject = mainProject;
0812: saveMainProject(mainProject);
0813: }
0814: pchSupport
0815: .firePropertyChange(PROPERTY_MAIN_PROJECT, null, null);
0816: }
0817:
0818: public synchronized List<Project> getRecentProjects() {
0819: return recentProjects.getProjects();
0820: }
0821:
0822: public synchronized boolean isRecentProjectsEmpty() {
0823: return recentProjects.isEmpty();
0824: }
0825:
0826: public synchronized List<UnloadedProjectInformation> getRecentProjectsInformation() {
0827: return recentProjects.getRecentProjectsInfo();
0828: }
0829:
0830: /** As this class is singletnon, which is not GCed it is good idea to
0831: *add WeakListeners or remove the listeners properly.
0832: */
0833:
0834: public void addPropertyChangeListener(PropertyChangeListener l) {
0835: pchSupport.addPropertyChangeListener(l);
0836: }
0837:
0838: public void removePropertyChangeListener(PropertyChangeListener l) {
0839: pchSupport.removePropertyChangeListener(l);
0840: }
0841:
0842: // Used from NewFile action
0843: public List<DataObject> getTemplatesLRU(Project project) {
0844: List<FileObject> pLRU = getTemplateNamesLRU(project);
0845: List<DataObject> templates = new ArrayList<DataObject>();
0846: FileSystem sfs = Repository.getDefault().getDefaultFileSystem();
0847: for (Iterator<FileObject> it = pLRU.iterator(); it.hasNext();) {
0848: FileObject fo = it.next();
0849: if (fo != null) {
0850: try {
0851: DataObject dobj = DataObject.find(fo);
0852: templates.add(dobj);
0853: } catch (DataObjectNotFoundException e) {
0854: it.remove();
0855: org.openide.ErrorManager.getDefault().notify(
0856: org.openide.ErrorManager.INFORMATIONAL, e);
0857: }
0858: } else {
0859: it.remove();
0860: }
0861: }
0862:
0863: return templates;
0864: }
0865:
0866: // Used from NewFile action
0867: public synchronized void updateTemplatesLRU(FileObject template) {
0868:
0869: String templateName = template.getPath();
0870:
0871: if (getRecentTemplates().contains(templateName)) {
0872: getRecentTemplates().remove(templateName);
0873: }
0874: getRecentTemplates().add(0, templateName);
0875:
0876: if (getRecentTemplates().size() > 100) {
0877: getRecentTemplates().remove(100);
0878: }
0879:
0880: OpenProjectListSettings.getInstance().setRecentTemplates(
0881: new ArrayList<String>(getRecentTemplates()));
0882: }
0883:
0884: // Package private methods -------------------------------------------------
0885:
0886: // Used from ProjectUiModule
0887: static void shutdown() {
0888: if (INSTANCE != null) {
0889: Iterator it = INSTANCE.openProjects.iterator();
0890: while (it.hasNext()) {
0891: Project p = (Project) it.next();
0892: notifyClosed(p);
0893: }
0894: }
0895: }
0896:
0897: // Used from OpenProjectAction
0898: public static Project fileToProject(File projectDir) {
0899:
0900: try {
0901:
0902: FileObject fo = FileUtil.toFileObject(projectDir);
0903: if (fo != null && /* #60518 */fo.isFolder()) {
0904: return ProjectManager.getDefault().findProject(fo);
0905: } else {
0906: return null;
0907: }
0908:
0909: } catch (IOException e) {
0910: /* Ignore; will be reported e.g. by ProjectChooserAccessory:
0911: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
0912: */
0913: return null;
0914: }
0915:
0916: }
0917:
0918: // Private methods ---------------------------------------------------------
0919:
0920: private static LinkedList<Project> URLs2Projects(
0921: Collection<URL> URLs) {
0922: LinkedList<Project> result = new LinkedList<Project>();
0923:
0924: for (URL url : URLs) {
0925: FileObject dir = URLMapper.findFileObject(url);
0926: if (dir != null && dir.isFolder()) {
0927: try {
0928: Project p = ProjectManager.getDefault()
0929: .findProject(dir);
0930: if (p != null) {
0931: result.add(p);
0932: }
0933: } catch (Throwable t) {
0934: //something bad happened during loading the project.
0935: //log the problem, but allow the other projects to be load
0936: //see issue #65900
0937: if (t instanceof ThreadDeath) {
0938: throw (ThreadDeath) t;
0939: }
0940:
0941: ErrorManager.getDefault().notify(
0942: ErrorManager.INFORMATIONAL, t);
0943: }
0944: }
0945: }
0946:
0947: return result;
0948: }
0949:
0950: private static List<URL> projects2URLs(Collection<Project> projects) {
0951: ArrayList<URL> URLs = new ArrayList<URL>(projects.size());
0952: for (Project p : projects) {
0953: try {
0954: URL root = p.getProjectDirectory().getURL();
0955: if (root != null) {
0956: URLs.add(root);
0957: }
0958: } catch (FileStateInvalidException e) {
0959: ErrorManager.getDefault().notify(
0960: ErrorManager.INFORMATIONAL, e);
0961: }
0962: }
0963:
0964: return URLs;
0965: }
0966:
0967: private static void notifyOpened(Project p) {
0968: for (Iterator i = p.getLookup().lookupAll(
0969: ProjectOpenedHook.class).iterator(); i.hasNext();) {
0970: ProjectOpenedHook hook = (ProjectOpenedHook) i.next();
0971:
0972: try {
0973: ProjectOpenedTrampoline.DEFAULT.projectOpened(hook);
0974: } catch (RuntimeException e) {
0975: LOGGER.log(Level.WARNING, null, e);
0976: // Do not try to call its close hook if its open hook already failed:
0977: INSTANCE.openProjects.remove(p);
0978: INSTANCE.removeModuleInfo(p);
0979: } catch (Error e) {
0980: LOGGER.log(Level.WARNING, null, e);
0981: INSTANCE.openProjects.remove(p);
0982: INSTANCE.removeModuleInfo(p);
0983: }
0984: }
0985: }
0986:
0987: private static void notifyClosed(Project p) {
0988: for (Iterator i = p.getLookup().lookupAll(
0989: ProjectOpenedHook.class).iterator(); i.hasNext();) {
0990: ProjectOpenedHook hook = (ProjectOpenedHook) i.next();
0991:
0992: try {
0993: ProjectOpenedTrampoline.DEFAULT.projectClosed(hook);
0994: } catch (RuntimeException e) {
0995: LOGGER.log(Level.WARNING, null, e);
0996: } catch (Error e) {
0997: LOGGER.log(Level.WARNING, null, e);
0998: }
0999: }
1000: }
1001:
1002: private boolean doOpenProject(final Project p) {
1003: boolean recentProjectsChanged;
1004: LOGGER
1005: .finer("doOpenProject(): opening project "
1006: + p.toString());
1007: synchronized (this ) {
1008: if (openProjects.contains(p)) {
1009: return false;
1010: }
1011: openProjects.add(p);
1012: addModuleInfo(p);
1013:
1014: p.getProjectDirectory().addFileChangeListener(
1015: deleteListener);
1016: p.getProjectDirectory().addFileChangeListener(
1017: nbprojectDeleteListener);
1018:
1019: recentProjectsChanged = recentProjects.remove(p);
1020: }
1021: logProjects("doOpenProject(): openProjects == ", openProjects
1022: .toArray(new Project[0])); // NOI18N
1023: // Notify projects opened
1024: notifyOpened(p);
1025:
1026: Mutex.EVENT.readAccess(new Action<Void>() {
1027: public Void run() {
1028: // Open project files
1029: ProjectUtilities.openProjectFiles(p);
1030:
1031: return null;
1032: }
1033: });
1034:
1035: return recentProjectsChanged;
1036: }
1037:
1038: private static List<Project> loadProjectList() {
1039: List<URL> URLs = OpenProjectListSettings.getInstance()
1040: .getOpenProjectsURLs();
1041: List<String> names = OpenProjectListSettings.getInstance()
1042: .getOpenProjectsDisplayNames();
1043: List<ExtIcon> icons = OpenProjectListSettings.getInstance()
1044: .getOpenProjectsIcons();
1045: List<Project> projects = new ArrayList<Project>();
1046:
1047: Iterator<URL> urlIt = URLs.iterator();
1048: Iterator<String> namesIt = names.iterator();
1049: Iterator<ExtIcon> iconIt = icons.iterator();
1050:
1051: while (urlIt.hasNext() && namesIt.hasNext() && iconIt.hasNext()) {
1052: projects.add(new LazyProject(urlIt.next(), namesIt.next(),
1053: iconIt.next()));
1054: }
1055:
1056: //List<Project> projects = URLs2Projects( URLs );
1057:
1058: return projects;
1059: }
1060:
1061: private static void saveProjectList(List<Project> projects) {
1062: List<URL> URLs = projects2URLs(projects);
1063: OpenProjectListSettings.getInstance().setOpenProjectsURLs(URLs);
1064: List<String> names = new ArrayList<String>();
1065: List<ExtIcon> icons = new ArrayList<ExtIcon>();
1066: for (Iterator<Project> it = projects.iterator(); it.hasNext();) {
1067: ProjectInformation prjInfo = ProjectUtils.getInformation(it
1068: .next());
1069: names.add(prjInfo.getDisplayName());
1070: ExtIcon extIcon = new ExtIcon();
1071: extIcon.setIcon(prjInfo.getIcon());
1072: icons.add(extIcon);
1073: }
1074: OpenProjectListSettings.getInstance()
1075: .setOpenProjectsDisplayNames(names);
1076: OpenProjectListSettings.getInstance().setOpenProjectsIcons(
1077: icons);
1078: }
1079:
1080: private static void saveMainProject(Project mainProject) {
1081: try {
1082: URL mainRoot = mainProject == null ? null : mainProject
1083: .getProjectDirectory().getURL();
1084: OpenProjectListSettings.getInstance().setMainProjectURL(
1085: mainRoot);
1086: } catch (FileStateInvalidException e) {
1087: OpenProjectListSettings.getInstance().setMainProjectURL(
1088: null);
1089: }
1090: }
1091:
1092: private ArrayList<FileObject> getTemplateNamesLRU(Project project) {
1093: // First take recently used templates and try to find those which
1094: // are supported by the project.
1095:
1096: ArrayList<FileObject> result = new ArrayList<FileObject>(
1097: NUM_TEMPLATES);
1098:
1099: RecommendedTemplates rt = project.getLookup().lookup(
1100: RecommendedTemplates.class);
1101: String rtNames[] = rt == null ? new String[0] : rt
1102: .getRecommendedTypes();
1103: PrivilegedTemplates pt = project.getLookup().lookup(
1104: PrivilegedTemplates.class);
1105: String ptNames[] = pt == null ? null : pt
1106: .getPrivilegedTemplates();
1107: ArrayList<String> privilegedTemplates = new ArrayList<String>(
1108: Arrays.asList(pt == null ? new String[0] : ptNames));
1109: FileSystem sfs = Repository.getDefault().getDefaultFileSystem();
1110:
1111: synchronized (this ) {
1112: Iterator<String> it = getRecentTemplates().iterator();
1113: for (int i = 0; i < NUM_TEMPLATES && it.hasNext(); i++) {
1114: String templateName = it.next();
1115: FileObject fo = sfs.findResource(templateName);
1116: if (fo == null) {
1117: it.remove(); // Does not exists remove
1118: } else if (isRecommended(project, fo)) {
1119: result.add(fo);
1120: privilegedTemplates.remove(templateName); // Not to have it twice
1121: } else {
1122: continue;
1123: }
1124: }
1125: }
1126:
1127: // If necessary fill the list with the rest of privileged templates
1128: Iterator<String> it = privilegedTemplates.iterator();
1129: for (int i = result.size(); i < NUM_TEMPLATES && it.hasNext(); i++) {
1130: String path = it.next();
1131: FileObject fo = sfs.findResource(path);
1132: if (fo != null) {
1133: result.add(fo);
1134: }
1135: }
1136:
1137: return result;
1138:
1139: }
1140:
1141: static boolean isRecommended(Project p, FileObject primaryFile) {
1142: if (getRecommendedTypes(p) == null
1143: || getRecommendedTypes(p).length == 0) {
1144: // if no recommendedTypes are supported (i.e. freeform) -> disaply all templates
1145: return true;
1146: }
1147:
1148: Object o = primaryFile.getAttribute("templateCategory"); // NOI18N
1149: if (o != null) {
1150: assert o instanceof String : primaryFile
1151: + " attr templateCategory = " + o;
1152: Iterator categoriesIt = getCategories((String) o)
1153: .iterator();
1154: boolean ok = false;
1155: while (categoriesIt.hasNext()) {
1156: String category = (String) categoriesIt.next();
1157: if (Arrays.asList(getRecommendedTypes(p)).contains(
1158: category)) {
1159: ok = true;
1160: break;
1161: }
1162: }
1163: return ok;
1164: } else {
1165: // issue 44871, if attr 'templateCategorized' is not set => all is ok
1166: // no category set, ok display it
1167: return true;
1168: }
1169: }
1170:
1171: private static String[] getRecommendedTypes(Project project) {
1172: RecommendedTemplates rt = project.getLookup().lookup(
1173: RecommendedTemplates.class);
1174: return rt == null ? null : rt.getRecommendedTypes();
1175: }
1176:
1177: private static List<String> getCategories(String source) {
1178: ArrayList<String> categories = new ArrayList<String>();
1179: StringTokenizer cattok = new StringTokenizer(source, ","); // NOI18N
1180: while (cattok.hasMoreTokens()) {
1181: categories.add(cattok.nextToken().trim());
1182: }
1183: return categories;
1184: }
1185:
1186: // Private innerclasses ----------------------------------------------------
1187:
1188: /** Maintains recent project list
1189: */
1190: private class RecentProjectList {
1191:
1192: private List<ProjectReference> recentProjects;
1193: private List<UnloadedProjectInformation> recentProjectsInfos;
1194:
1195: private int size;
1196:
1197: /**
1198: *@size Max number of the project list.
1199: */
1200: public RecentProjectList(int size) {
1201: this .size = size;
1202: recentProjects = new ArrayList<ProjectReference>(size);
1203: recentProjectsInfos = new ArrayList<UnloadedProjectInformation>(
1204: size);
1205: if (LOGGER.isLoggable(LOG_LEVEL)) {
1206: LOGGER.log(LOG_LEVEL,
1207: "created a RecentProjectList: size=" + size);
1208: }
1209: }
1210:
1211: public void add(Project p) {
1212: int index = getIndex(p);
1213:
1214: if (index == -1) {
1215: // Project not in list
1216: if (LOGGER.isLoggable(LOG_LEVEL)) {
1217: LOGGER.log(LOG_LEVEL, "add new recent project: "
1218: + p);
1219: }
1220: if (recentProjects.size() == size) {
1221: // Need some space for the newly added project
1222: recentProjects.remove(size - 1);
1223: recentProjectsInfos.remove(size - 1);
1224: }
1225: recentProjects.add(0, new ProjectReference(p));
1226: try {
1227: recentProjectsInfos.add(0,
1228: ProjectInfoAccessor.DEFAULT.getProjectInfo(
1229: ProjectUtils.getInformation(p)
1230: .getDisplayName(),
1231: ProjectUtils.getInformation(p)
1232: .getIcon(), p
1233: .getProjectDirectory()
1234: .getURL()));
1235: } catch (FileStateInvalidException ex) {
1236: ErrorManager.getDefault().notify(
1237: ErrorManager.INFORMATIONAL, ex);
1238: }
1239: } else {
1240: if (LOGGER.isLoggable(LOG_LEVEL)) {
1241: LOGGER
1242: .log(LOG_LEVEL, "re-add recent project: "
1243: + p);
1244: }
1245: // Project is in list => just move it to first place
1246: recentProjects.remove(index);
1247: recentProjects.add(0, new ProjectReference(p));
1248: recentProjectsInfos.remove(index);
1249: try {
1250: recentProjectsInfos.add(0,
1251: ProjectInfoAccessor.DEFAULT.getProjectInfo(
1252: ProjectUtils.getInformation(p)
1253: .getDisplayName(),
1254: ProjectUtils.getInformation(p)
1255: .getIcon(), p
1256: .getProjectDirectory()
1257: .getURL()));
1258: } catch (FileStateInvalidException ex) {
1259: ErrorManager.getDefault().notify(
1260: ErrorManager.INFORMATIONAL, ex);
1261: }
1262: }
1263: }
1264:
1265: public boolean remove(Project p) {
1266: int index = getIndex(p);
1267: if (index != -1) {
1268: if (LOGGER.isLoggable(LOG_LEVEL)) {
1269: LOGGER
1270: .log(LOG_LEVEL, "remove recent project: "
1271: + p);
1272: }
1273: recentProjects.remove(index);
1274: recentProjectsInfos.remove(index);
1275: return true;
1276: }
1277: return false;
1278: }
1279:
1280: public void refresh() {
1281: assert recentProjects.size() == recentProjectsInfos.size();
1282: boolean refresh = false;
1283: int index = 0;
1284: for (ProjectReference prjRef : recentProjects) {
1285: URL url = prjRef.getURL();
1286: FileObject fo = null;
1287: try {
1288: fo = FileUtil.toFileObject(new File(url.toURI()));
1289: } catch (URISyntaxException use) {
1290: //
1291: }
1292: if (fo == null) { // externally deleted project
1293: refresh = true;
1294: break;
1295: } else if (fo.getFileObject("nbproject") == null
1296: || !fo.getFileObject("nbproject").isValid()) {
1297: fo
1298: .removeFileChangeListener(nbprojectDeleteListener);
1299: refresh = true;
1300: break;
1301: }
1302: index++;
1303: }
1304: if (refresh) {
1305: recentProjects.remove(index);
1306: recentProjectsInfos.remove(index);
1307: pchSupport.firePropertyChange(PROPERTY_RECENT_PROJECTS,
1308: null, null);
1309: save();
1310: }
1311: }
1312:
1313: public List<Project> getProjects() {
1314: List<Project> result = new ArrayList<Project>(
1315: recentProjects.size());
1316: // Copy the list
1317: List<ProjectReference> references = new ArrayList<ProjectReference>(
1318: recentProjects);
1319: for (Iterator<ProjectReference> it = references.iterator(); it
1320: .hasNext();) {
1321: ProjectReference pRef = it.next();
1322: Project p = pRef.getProject();
1323: if (p == null || !p.getProjectDirectory().isValid()) {
1324: remove(p); // Folder does not exist any more => remove from
1325: if (LOGGER.isLoggable(LOG_LEVEL)) {
1326: LOGGER.log(LOG_LEVEL,
1327: "removing dead recent project: " + p);
1328: }
1329: } else {
1330: result.add(p);
1331: }
1332: }
1333: if (LOGGER.isLoggable(LOG_LEVEL)) {
1334: LOGGER.log(LOG_LEVEL, "recent projects: " + result);
1335: }
1336: return result;
1337: }
1338:
1339: public boolean isEmpty() {
1340: boolean empty = recentProjects.isEmpty();
1341: if (LOGGER.isLoggable(LOG_LEVEL)) {
1342: LOGGER
1343: .log(LOG_LEVEL, "recent projects empty? "
1344: + empty);
1345: }
1346: return empty;
1347: }
1348:
1349: public void load() {
1350: List<URL> URLs = OpenProjectListSettings.getInstance()
1351: .getRecentProjectsURLs();
1352: List<String> names = OpenProjectListSettings.getInstance()
1353: .getRecentProjectsDisplayNames();
1354: List<ExtIcon> icons = OpenProjectListSettings.getInstance()
1355: .getRecentProjectsIcons();
1356: if (LOGGER.isLoggable(LOG_LEVEL)) {
1357: LOGGER.log(LOG_LEVEL, "recent project list load: "
1358: + URLs);
1359: }
1360: recentProjects.clear();
1361: for (Iterator it = URLs.iterator(); it.hasNext();) {
1362: recentProjects
1363: .add(new ProjectReference((URL) it.next()));
1364: }
1365: recentProjectsInfos.clear();
1366: for (Iterator iterNames = names.iterator(), iterURLs = URLs
1367: .iterator(), iterIcons = icons.iterator(); (iterNames
1368: .hasNext()
1369: && iterURLs.hasNext() && iterIcons.hasNext());) {
1370: String name = (String) iterNames.next();
1371: URL url = (URL) iterURLs.next();
1372: Icon icon = ((ExtIcon) iterIcons.next()).getIcon();
1373: recentProjectsInfos.add(ProjectInfoAccessor.DEFAULT
1374: .getProjectInfo(name, icon, url));
1375: }
1376: // if following is true then there was either some problem with serialization
1377: // or user started new IDE on userdir with only partial information saved - only URLs
1378: // then both list should be cleared - recent project information will be lost
1379: if (recentProjects.size() != recentProjectsInfos.size()) {
1380: recentProjects.clear();
1381: recentProjectsInfos.clear();
1382: }
1383: // register project delete listener to all open projects
1384: synchronized (this ) {
1385: for (Project p : openProjects) {
1386: assert p != null : "There is null in "
1387: + openProjects;
1388: assert p.getProjectDirectory() != null : "Project "
1389: + p + " has null project directory";
1390: p.getProjectDirectory().addFileChangeListener(
1391: nbprojectDeleteListener);
1392: }
1393: }
1394: }
1395:
1396: public void save() {
1397: List<URL> URLs = new ArrayList<URL>(recentProjects.size());
1398: for (ProjectReference pRef : recentProjects) {
1399: URL pURL = pRef.getURL();
1400: if (pURL != null) {
1401: URLs.add(pURL);
1402: }
1403: }
1404: if (LOGGER.isLoggable(LOG_LEVEL)) {
1405: LOGGER.log(LOG_LEVEL, "recent project list save: "
1406: + URLs);
1407: }
1408: OpenProjectListSettings.getInstance()
1409: .setRecentProjectsURLs(URLs);
1410: int listSize = recentProjectsInfos.size();
1411: List<String> names = new ArrayList<String>(listSize);
1412: List<ExtIcon> icons = new ArrayList<ExtIcon>(listSize);
1413: for (Iterator it = recentProjectsInfos.iterator(); it
1414: .hasNext();) {
1415: UnloadedProjectInformation prjInfo = (UnloadedProjectInformation) it
1416: .next();
1417: names.add(prjInfo.getDisplayName());
1418: ExtIcon extIcon = new ExtIcon();
1419: extIcon.setIcon(prjInfo.getIcon());
1420: icons.add(extIcon);
1421: }
1422: OpenProjectListSettings.getInstance()
1423: .setRecentProjectsDisplayNames(names);
1424: OpenProjectListSettings.getInstance()
1425: .setRecentProjectsIcons(icons);
1426: }
1427:
1428: private int getIndex(Project p) {
1429:
1430: URL pURL;
1431: try {
1432: if (p == null || p.getProjectDirectory() == null) {
1433: return -1;
1434: }
1435: pURL = p.getProjectDirectory().getURL();
1436: } catch (FileStateInvalidException e) {
1437: return -1;
1438: }
1439:
1440: int i = 0;
1441:
1442: for (Iterator it = recentProjects.iterator(); it.hasNext(); i++) {
1443: URL p2URL = ((ProjectReference) it.next()).getURL();
1444: if (pURL.equals(p2URL)) {
1445: return i;
1446: }
1447: }
1448:
1449: return -1;
1450: }
1451:
1452: private List<UnloadedProjectInformation> getRecentProjectsInfo() {
1453: return recentProjectsInfos;
1454: }
1455:
1456: private class ProjectReference {
1457:
1458: private WeakReference<Project> projectReference;
1459: private URL projectURL;
1460:
1461: public ProjectReference(URL url) {
1462: this .projectURL = url;
1463: }
1464:
1465: public ProjectReference(Project p) {
1466: this .projectReference = new WeakReference<Project>(p);
1467: try {
1468: projectURL = p.getProjectDirectory().getURL();
1469: } catch (FileStateInvalidException e) {
1470: if (LOGGER.isLoggable(LOG_LEVEL)) {
1471: LOGGER.log(LOG_LEVEL,
1472: "FSIE getting URL for project: "
1473: + p.getProjectDirectory());
1474: }
1475: }
1476: }
1477:
1478: public Project getProject() {
1479:
1480: Project p = null;
1481:
1482: if (projectReference != null) { // Reference to project exists
1483: p = projectReference.get();
1484: if (p != null) {
1485: // And refers to some project, check for validity:
1486: if (ProjectManager.getDefault().isValid(p))
1487: return p;
1488: else
1489: return null;
1490: }
1491: }
1492:
1493: if (LOGGER.isLoggable(LOG_LEVEL)) {
1494: LOGGER.log(LOG_LEVEL,
1495: "no active project reference for "
1496: + projectURL);
1497: }
1498: if (projectURL != null) {
1499: FileObject dir = URLMapper
1500: .findFileObject(projectURL);
1501: if (dir != null && dir.isFolder()) {
1502: try {
1503: p = ProjectManager.getDefault()
1504: .findProject(dir);
1505: if (p != null) {
1506: projectReference = new WeakReference<Project>(
1507: p);
1508: if (LOGGER.isLoggable(LOG_LEVEL)) {
1509: LOGGER.log(LOG_LEVEL, "found " + p);
1510: }
1511: return p;
1512: }
1513: } catch (IOException e) {
1514: // Ignore invalid folders
1515: if (LOGGER.isLoggable(LOG_LEVEL)) {
1516: LOGGER.log(LOG_LEVEL,
1517: "could not load recent project from "
1518: + projectURL);
1519: }
1520: }
1521: }
1522: }
1523:
1524: if (LOGGER.isLoggable(LOG_LEVEL)) {
1525: LOGGER.log(LOG_LEVEL, "no recent project in "
1526: + projectURL);
1527: }
1528: return null; // Empty reference
1529: }
1530:
1531: public URL getURL() {
1532: return projectURL;
1533: }
1534:
1535: }
1536:
1537: }
1538:
1539: public static class ProjectByDisplayNameComparator implements
1540: Comparator<Project> {
1541:
1542: private static Comparator<Object> COLLATOR = Collator
1543: .getInstance();
1544:
1545: public int compare(Project p1, Project p2) {
1546: // Uncoment to make the main project be the first one
1547: // but then needs to listen to main project change
1548: // if ( OpenProjectList.getDefault().isMainProject( p1 ) ) {
1549: // return -1;
1550: // }
1551: //
1552: // if ( OpenProjectList.getDefault().isMainProject( p2 ) ) {
1553: // return 1;
1554: // }
1555:
1556: String n1 = ProjectUtils.getInformation(p1)
1557: .getDisplayName();
1558: String n2 = ProjectUtils.getInformation(p2)
1559: .getDisplayName();
1560: if (n1 != null && n2 != null) {
1561: return COLLATOR.compare(n1, n2);
1562: } else if (n1 == null && n2 != null) {
1563: LOGGER
1564: .log(
1565: Level.WARNING,
1566: p1
1567: + ": ProjectInformation.getDisplayName() should not return null!");
1568: return -1;
1569: } else if (n1 != null && n2 == null) {
1570: LOGGER
1571: .log(
1572: Level.WARNING,
1573: p2
1574: + ": ProjectInformation.getDisplayName() should not return null!");
1575: return 1;
1576: }
1577: return 0; // both null
1578:
1579: }
1580:
1581: }
1582:
1583: private final class NbProjectDeletionListener extends
1584: FileChangeAdapter {
1585:
1586: public NbProjectDeletionListener() {
1587: }
1588:
1589: @Override
1590: public void fileDeleted(FileEvent fe) {
1591: if (fe.getFile().getName().equals("nbproject")) {
1592: recentProjects.refresh();
1593: }
1594: }
1595:
1596: }
1597:
1598: /**
1599: * Closesdeleted projects.
1600: */
1601: private final class ProjectDeletionListener extends
1602: FileChangeAdapter {
1603:
1604: public ProjectDeletionListener() {
1605: }
1606:
1607: public @Override
1608: void fileDeleted(FileEvent fe) {
1609: synchronized (OpenProjectList.this ) {
1610: Project toRemove = null;
1611: for (Project prj : openProjects) {
1612: if (fe.getFile().equals(prj.getProjectDirectory())) {
1613: toRemove = prj;
1614: break;
1615: }
1616: }
1617: final Project fRemove = toRemove;
1618: if (fRemove != null) {
1619: //#108376 avoid deadlock in org.netbeans.modules.project.ui.ProjectUtilities$1.close(ProjectUtilities.java:106)
1620: // alternatively removing the close() metod from synchronized block could help as well..
1621: SwingUtilities.invokeLater(new Runnable() {
1622: public void run() {
1623: close(new Project[] { fRemove }, false);
1624: }
1625: });
1626: }
1627: }
1628: }
1629:
1630: }
1631:
1632: private static ModuleInfo findModuleForProject(Project prj) {
1633: Collection<? extends ModuleInfo> instances = Lookup
1634: .getDefault().lookupAll(ModuleInfo.class);
1635: ModuleInfo info = null;
1636: for (ModuleInfo cur : instances) {
1637: if (!cur.isEnabled()) {
1638: continue;
1639: }
1640: if (cur.getClassLoader() == prj.getClass().getClassLoader()) {
1641: info = cur;
1642: break;
1643: }
1644: }
1645: return info;
1646: }
1647:
1648: private void addModuleInfo(Project prj) {
1649: ModuleInfo info = findModuleForProject(prj);
1650: if (info != null) {
1651: // is null in tests..
1652: synchronized (openProjectsModuleInfos) {
1653: if (!openProjectsModuleInfos.containsKey(info)) {
1654: openProjectsModuleInfos.put(info,
1655: new ArrayList<Project>());
1656: info.addPropertyChangeListener(infoListener);
1657: }
1658: openProjectsModuleInfos.get(info).add(prj);
1659: }
1660: }
1661: }
1662:
1663: private void removeModuleInfo(Project prj) {
1664: ModuleInfo info = findModuleForProject(prj);
1665: removeModuleInfo(prj, info);
1666: }
1667:
1668: private void removeModuleInfo(Project prj, ModuleInfo info) {
1669: // info can be null in case we are closing a project from disabled module
1670: if (info != null) {
1671: synchronized (openProjectsModuleInfos) {
1672: List<Project> prjlist = openProjectsModuleInfos
1673: .get(info);
1674: if (prjlist != null) {
1675: prjlist.remove(prj);
1676: if (prjlist.size() == 0) {
1677: info.removePropertyChangeListener(infoListener);
1678: openProjectsModuleInfos.remove(info);
1679: }
1680: }
1681: }
1682: }
1683: }
1684:
1685: private void checkModuleInfo(ModuleInfo info) {
1686: if (info.isEnabled()) {
1687: return;
1688: }
1689: Collection<Project> toRemove = new ArrayList<Project>(
1690: openProjectsModuleInfos.get(info));
1691: if (toRemove != null && toRemove.size() > 0) {
1692: for (Project prj : toRemove) {
1693: removeModuleInfo(prj, info);
1694: }
1695: close(toRemove.toArray(new Project[toRemove.size()]), false);
1696: }
1697: }
1698:
1699: private static LogRecord[] createRecord(String msg,
1700: Project[] projects) {
1701: if (projects.length == 0) {
1702: return null;
1703: }
1704:
1705: Map<String, int[]> counts = new HashMap<String, int[]>();
1706: for (Project p : projects) {
1707: String n = p.getClass().getName();
1708: int[] cnt = counts.get(n);
1709: if (cnt == null) {
1710: cnt = new int[1];
1711: counts.put(n, cnt);
1712: }
1713: cnt[0]++;
1714: }
1715:
1716: Logger logger = Logger.getLogger("org.netbeans.ui.projects"); // NOI18N
1717: LogRecord[] arr = new LogRecord[counts.size()];
1718: int i = 0;
1719: for (Map.Entry<String, int[]> entry : counts.entrySet()) {
1720: LogRecord rec = new LogRecord(Level.CONFIG, msg);
1721: rec
1722: .setParameters(new Object[] { entry.getKey(),
1723: afterLastDot(entry.getKey()),
1724: entry.getValue()[0] });
1725: rec.setLoggerName(logger.getName());
1726: rec.setResourceBundle(NbBundle
1727: .getBundle(OpenProjectList.class));
1728: rec.setResourceBundleName(OpenProjectList.class
1729: .getPackage().getName()
1730: + ".Bundle");
1731:
1732: arr[i++] = rec;
1733: }
1734:
1735: return arr;
1736: }
1737:
1738: private static void log(LogRecord[] arr) {
1739: if (arr == null) {
1740: return;
1741: }
1742: Logger logger = Logger.getLogger("org.netbeans.ui.projects"); // NOI18N
1743: for (LogRecord r : arr) {
1744: logger.log(r);
1745: }
1746: }
1747:
1748: private static String afterLastDot(String s) {
1749: int index = s.lastIndexOf('.');
1750: if (index == -1) {
1751: return s;
1752: }
1753: return s.substring(index + 1);
1754: }
1755:
1756: private static void logProjects(String message, Project[] projects) {
1757: if (projects.length == 0) {
1758: return;
1759: }
1760: for (Project p : projects) {
1761: LOGGER.finer(message + p.toString());
1762: }
1763: }
1764:
1765: }
|