001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.versioning.system.cvss.ui.actions.project;
043:
044: import org.openide.util.actions.NodeAction;
045: import org.openide.util.*;
046: import org.openide.nodes.Node;
047: import org.openide.filesystems.FileObject;
048: import org.openide.filesystems.FileUtil;
049: import org.openide.*;
050: import org.openide.loaders.DataObject;
051: import org.openide.loaders.DataShadow;
052: import org.netbeans.api.project.*;
053: import org.netbeans.api.queries.SharabilityQuery;
054: import org.netbeans.modules.versioning.system.cvss.CvsModuleConfig;
055: import org.netbeans.modules.versioning.system.cvss.ui.wizards.RepositoryStep;
056: import org.netbeans.modules.versioning.system.cvss.ui.wizards.AbstractStep;
057: import org.netbeans.modules.versioning.system.cvss.ui.selectors.ModuleSelector;
058: import org.netbeans.modules.versioning.system.cvss.FileInformation;
059: import org.netbeans.modules.versioning.system.cvss.FileStatusCache;
060: import org.netbeans.modules.versioning.system.cvss.CvsVersioningSystem;
061: import org.netbeans.modules.versioning.system.cvss.ExecutorGroup;
062: import org.netbeans.lib.cvsclient.command.importcmd.ImportCommand;
063: import org.netbeans.lib.cvsclient.command.GlobalOptions;
064: import org.netbeans.lib.cvsclient.CVSRoot;
065: import org.netbeans.spi.project.ui.support.ProjectChooser;
066:
067: import javax.swing.*;
068: import javax.swing.event.DocumentListener;
069: import javax.swing.event.DocumentEvent;
070: import javax.swing.event.ChangeListener;
071: import javax.swing.event.ChangeEvent;
072: import java.io.*;
073: import java.util.*;
074: import java.text.MessageFormat;
075: import java.awt.event.ActionListener;
076: import java.awt.event.ActionEvent;
077:
078: /**
079: * Imports folder into CVS repository. It's enabled on Nodes that represent:
080: * <ul>
081: * <li>project root directory, parent of all necessary
082: * project data and metadata.
083: * <li>folders that are not a part on any project
084: * </ul>
085: * It's minimalitics attempt to assure
086: * that the project can be reopend after checkout.
087: * It also simplifies implemenattion avoiding huge
088: * import mapping wizard for projects with external
089: * data folders.
090: *
091: * <p>Before actual CVS <tt>import</tt> it recursively scans
092: * imported context and prepares <tt>.cvsignore</tt> files.
093: * After <tt>import</tt> it optionally turns imported context
094: * into versioned using <tt>checkout</tt> and copying respective
095: * CVS metadata.
096: *
097: * @author Petr Kuzel
098: */
099: public final class AddToRepositoryAction extends NodeAction implements
100: ChangeListener {
101:
102: private static final String RECENT_ROOTS = "addToRepositoryAction.recentRoots";
103:
104: private WizardDescriptor wizard;
105:
106: private WizardDescriptor.Iterator wizardIterator;
107:
108: private RepositoryStep repositoryStep;
109: private ImportStep importStep;
110:
111: public AddToRepositoryAction() {
112: setIcon(null);
113: putValue("noIconInMenu", Boolean.TRUE); // NOI18N
114: }
115:
116: public String getName() {
117: return NbBundle.getMessage(AddToRepositoryAction.class,
118: "BK0006");
119: }
120:
121: public HelpCtx getHelpCtx() {
122: return null;
123: }
124:
125: protected boolean enable(Node[] nodes) {
126: if (nodes.length == 1) {
127: FileStatusCache cache = CvsVersioningSystem.getInstance()
128: .getStatusCache();
129: File dir = lookupImportDirectory(nodes[0]);
130: if (dir != null && dir.isDirectory()) {
131: FileInformation status = cache.getStatus(dir);
132: // mutually exclusive enablement logic with commit
133: if ((status.getStatus() & FileInformation.STATUS_MANAGED) == 0) {
134: // do not allow to import partial/nonatomic project, all must lie under imported common root
135: FileObject fo = FileUtil.toFileObject(dir);
136: Project p = FileOwnerQuery.getOwner(fo);
137: if (p == null) {
138: return true;
139: }
140: FileObject projectDir = p.getProjectDirectory();
141: return FileUtil.isParentOf(projectDir, fo) == false;
142: }
143: }
144: }
145: return false;
146: }
147:
148: protected void performAction(Node[] nodes) {
149: if (nodes.length == 1) {
150: final File importDirectory = lookupImportDirectory(nodes[0]);
151: if (importDirectory != null) {
152:
153: // try to detect some resonable defaults for cvs root and repositoryStep
154:
155: File parent = importDirectory.getParentFile();
156: File parent_cvsRoot = new File(parent, "CVS/Root"); // NOI18N
157: File parent_cvsRepo = new File(parent, "CVS/Repository"); // NOI18N
158: String cvsRoot = null;
159: String cvsRepository = null;
160: if (parent_cvsRepo.isFile() && parent_cvsRoot.isFile()) {
161: BufferedReader r = null;
162: try {
163: r = new BufferedReader((new FileReader(
164: parent_cvsRoot)));
165: cvsRoot = r.readLine();
166: } catch (IOException e) {
167: ErrorManager err = ErrorManager.getDefault();
168: err.annotate(e, NbBundle.getMessage(
169: AddToRepositoryAction.class, "BK0016"));
170: err.notify(e);
171: } finally {
172: if (r != null) {
173: try {
174: r.close();
175: } catch (IOException alreadyClosed) {
176: }
177: }
178: }
179:
180: try {
181: r = new BufferedReader((new FileReader(
182: parent_cvsRepo)));
183: cvsRepository = r.readLine();
184: } catch (IOException e) {
185: ErrorManager err = ErrorManager.getDefault();
186: err.annotate(e, NbBundle.getMessage(
187: AddToRepositoryAction.class, "BK0017"));
188: err.notify(e);
189: } finally {
190: if (r != null) {
191: try {
192: r.close();
193: } catch (IOException alreadyClosed) {
194: }
195: }
196: }
197: }
198:
199: String prefRoot;
200: if (cvsRoot != null) {
201: prefRoot = cvsRoot;
202: } else {
203: prefRoot = NbBundle.getMessage(
204: AddToRepositoryAction.class, "BK0008");
205: }
206:
207: String prefModule;
208: if (cvsRepository != null) {
209: prefModule = cvsRepository + "/"
210: + importDirectory.getName(); // NOI18N
211: } else {
212: prefModule = importDirectory.getName();
213: }
214:
215: wizardIterator = panelIterator(prefRoot, prefModule,
216: importDirectory.getAbsolutePath());
217: wizard = new WizardDescriptor(wizardIterator);
218: wizard.putProperty("WizardPanel_contentData", // NOI18N
219: new String[] {
220: NbBundle.getMessage(
221: AddToRepositoryAction.class,
222: "BK0015"),
223: NbBundle.getMessage(
224: AddToRepositoryAction.class,
225: "BK0014") });
226: wizard.putProperty("WizardPanel_contentDisplayed",
227: Boolean.TRUE); // NOI18N
228: wizard.putProperty("WizardPanel_autoWizardStyle",
229: Boolean.TRUE); // NOI18N
230: wizard.putProperty("WizardPanel_contentNumbered",
231: Boolean.TRUE); // NOI18N
232: wizard.setTitleFormat(new MessageFormat("{0}")); // NOI18N
233: String title = NbBundle.getMessage(
234: AddToRepositoryAction.class, "BK0007");
235: wizard.setTitle(title);
236:
237: Object result = DialogDisplayer.getDefault().notify(
238: wizard);
239: if (result == DialogDescriptor.OK_OPTION) {
240: RequestProcessor.getDefault().post(new Runnable() {
241: public void run() {
242: async(importDirectory);
243: }
244: });
245: }
246: }
247: }
248: }
249:
250: private void async(File importDirectory) {
251: boolean checkout = importStep.getCheckout();
252: String logMessage = importStep.getMessage();
253: String module = importStep.getModule();
254: String vendorTag = "default_vendor"; // NOI18N
255: String releaseTag = "default_release"; // NOI18N
256: String selectedRoot = repositoryStep.getCvsRoot();
257: String folder = importStep.getFolder();
258: File dir = new File(folder);
259:
260: org.netbeans.modules.versioning.util.Utils.insert(
261: CvsModuleConfig.getDefault().getPreferences(),
262: RECENT_ROOTS, selectedRoot, 8);
263:
264: ExecutorGroup group = new ExecutorGroup(NbBundle.getMessage(
265: AddToRepositoryAction.class, "BK0019"));
266: try {
267: group.progress(NbBundle.getMessage(
268: AddToRepositoryAction.class, "BK0020"));
269: group.addCancellable(new Cancellable() {
270: public boolean cancel() {
271: return true;
272: }
273: });
274: prepareIgnore(dir);
275: } catch (IOException e) {
276: group.executed();
277: ErrorManager err = ErrorManager.getDefault();
278: err.annotate(e, NbBundle.getMessage(
279: AddToRepositoryAction.class, "BK0021"));
280: err.notify(e);
281: return;
282: }
283:
284: GlobalOptions gtx = CvsVersioningSystem.createGlobalOptions();
285: gtx.setCVSRoot(selectedRoot);
286: ImportCommand importCommand = new ImportCommand();
287: importCommand.setModule(module);
288: importCommand.setLogMessage(logMessage);
289: importCommand.setVendorTag(vendorTag);
290: importCommand.setReleaseTag(releaseTag);
291: importCommand.setImportDirectory(importDirectory.getPath());
292:
293: new ImportExecutor(importCommand, gtx, checkout, folder, group); // joins the group
294: group.execute();
295: }
296:
297: public boolean cancel() {
298:
299: return true;
300: }
301:
302: private File lookupImportDirectory(Node node) {
303: File importDirectory = null;
304: Project project = (Project) node.getLookup().lookup(
305: Project.class);
306: if (project != null) {
307: Sources sources = ProjectUtils.getSources(project);
308: SourceGroup[] groups = sources
309: .getSourceGroups(Sources.TYPE_GENERIC);
310: if (groups.length == 1) {
311: FileObject root = groups[0].getRootFolder();
312: importDirectory = FileUtil.toFile(root);
313: } else {
314: importDirectory = FileUtil.toFile(project
315: .getProjectDirectory());
316: }
317: } else {
318: FileObject fo = null;
319: Collection fileObjects = node.getLookup().lookup(
320: new Lookup.Template(FileObject.class))
321: .allInstances();
322: if (fileObjects.size() > 0) {
323: fo = (FileObject) fileObjects.iterator().next();
324: } else {
325: DataObject dataObject = (DataObject) node
326: .getCookie(DataObject.class);
327: if (dataObject instanceof DataShadow) {
328: dataObject = ((DataShadow) dataObject)
329: .getOriginal();
330: }
331: if (dataObject != null) {
332: fo = dataObject.getPrimaryFile();
333: }
334: }
335:
336: if (fo != null) {
337: File f = FileUtil.toFile(fo);
338: if (f != null && f.isDirectory()) {
339: importDirectory = f;
340: }
341: }
342: }
343: return importDirectory;
344: }
345:
346: private WizardDescriptor.Iterator panelIterator(String root,
347: String module, String folder) {
348: repositoryStep = new RepositoryStep(
349: RepositoryStep.IMPORT_HELP_ID);
350: repositoryStep.initPreferedCvsRoot(root);
351: repositoryStep.addChangeListener(this );
352: importStep = new ImportStep(module, folder);
353: importStep.addChangeListener(this );
354:
355: final WizardDescriptor.Panel[] panels = new WizardDescriptor.Panel[2];
356: panels[0] = repositoryStep;
357: panels[1] = importStep;
358:
359: WizardDescriptor.ArrayIterator ret = new WizardDescriptor.ArrayIterator(
360: panels) {
361: public WizardDescriptor.Panel current() {
362: WizardDescriptor.Panel ret = super .current();
363: for (int i = 0; i < panels.length; i++) {
364: if (panels[i] == ret) {
365: wizard.putProperty(
366: "WizardPanel_contentSelectedIndex",
367: new Integer(i)); // NOI18N
368: }
369: }
370: return ret;
371: }
372: };
373: return ret;
374: }
375:
376: private void setErrorMessage(String msg) {
377: if (wizard != null) {
378: wizard.putProperty("WizardPanel_errorMessage", msg); // NOI18N
379: }
380: }
381:
382: public void stateChanged(ChangeEvent e) {
383: AbstractStep step = (AbstractStep) wizardIterator.current();
384: setErrorMessage(step.getErrorMessage());
385: }
386:
387: class ImportStep extends AbstractStep implements ActionListener {
388: private final String module;
389: private final String folder;
390: private ImportPanel importPanel;
391:
392: public ImportStep(String module, String folder) {
393: this .module = module;
394: this .folder = folder;
395: }
396:
397: public HelpCtx getHelp() {
398: return new HelpCtx(ImportStep.class);
399: }
400:
401: protected JComponent createComponent() {
402: importPanel = new ImportPanel();
403: importPanel.moduleTextField.setText(module);
404: importPanel.folderTextField.setText(folder);
405:
406: // user input validation
407: DocumentListener validation = new DocumentListener() {
408: public void changedUpdate(DocumentEvent e) {
409: }
410:
411: public void insertUpdate(DocumentEvent e) {
412: String s = checkInput(importPanel);
413: if (s == null) {
414: valid();
415: } else {
416: invalid(s);
417: }
418: }
419:
420: public void removeUpdate(DocumentEvent e) {
421: String s = checkInput(importPanel);
422: if (s == null) {
423: valid();
424: } else {
425: invalid(s);
426: }
427: }
428: };
429: importPanel.moduleTextField.getDocument()
430: .addDocumentListener(validation);
431: importPanel.commentTextArea.getDocument()
432: .addDocumentListener(validation);
433: importPanel.folderTextField.getDocument()
434: .addDocumentListener(validation);
435: importPanel.folderButton.addActionListener(this );
436: importPanel.moduleButton.addActionListener(this );
437:
438: String s = checkInput(importPanel);
439: if (s == null) {
440: valid();
441: } else {
442: invalid(s);
443: }
444:
445: return importPanel;
446: }
447:
448: protected void validateBeforeNext() {
449: }
450:
451: public boolean getCheckout() {
452: return importPanel.checkoutCheckBox.isSelected();
453: }
454:
455: public String getMessage() {
456: return importPanel.commentTextArea.getText();
457: }
458:
459: public String getModule() {
460: return importPanel.moduleTextField.getText();
461: }
462:
463: public String getFolder() {
464: return importPanel.folderTextField.getText();
465: }
466:
467: /**
468: * Returns file to be initaly used.
469: * <ul>
470: * <li>first is takes text in workTextField
471: * <li>then recent project folder
472: * <li>finally <tt>user.home</tt>
473: * <ul>
474: */
475: private File defaultWorkingDirectory() {
476: File defaultDir = null;
477: String current = importPanel.folderTextField.getText();
478: if (current != null && !(current.trim().equals(""))) { // NOI18N
479: File currentFile = new File(current);
480: while (currentFile != null
481: && currentFile.exists() == false) {
482: currentFile = currentFile.getParentFile();
483: }
484: if (currentFile != null) {
485: if (currentFile.isFile()) {
486: defaultDir = currentFile.getParentFile();
487: } else {
488: defaultDir = currentFile;
489: }
490: }
491: }
492:
493: if (defaultDir == null) {
494: File projectFolder = ProjectChooser.getProjectsFolder();
495: if (projectFolder.exists()
496: && projectFolder.isDirectory()) {
497: defaultDir = projectFolder;
498: }
499: }
500:
501: if (defaultDir == null) {
502: defaultDir = new File(System.getProperty("user.home")); // NOI18N
503: }
504:
505: return defaultDir;
506: }
507:
508: public void actionPerformed(ActionEvent e) {
509: if (e.getSource() == importPanel.folderButton) {
510: File defaultDir = defaultWorkingDirectory();
511: JFileChooser fileChooser = new JFileChooser(defaultDir);
512: fileChooser.setDialogTitle(NbBundle.getMessage(
513: AddToRepositoryAction.class, "BK1017"));
514: fileChooser.setMultiSelectionEnabled(false);
515: javax.swing.filechooser.FileFilter[] old = fileChooser
516: .getChoosableFileFilters();
517: for (int i = 0; i < old.length; i++) {
518: javax.swing.filechooser.FileFilter fileFilter = old[i];
519: fileChooser.removeChoosableFileFilter(fileFilter);
520:
521: }
522: fileChooser
523: .addChoosableFileFilter(new javax.swing.filechooser.FileFilter() {
524: public boolean accept(File f) {
525: return f.isDirectory();
526: }
527:
528: public String getDescription() {
529: return NbBundle.getMessage(
530: AddToRepositoryAction.class,
531: "BK1018");
532: }
533: });
534: fileChooser
535: .setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
536: fileChooser.showDialog(importPanel, NbBundle
537: .getMessage(AddToRepositoryAction.class,
538: "BK1019"));
539: File f = fileChooser.getSelectedFile();
540: if (f != null) {
541: importPanel.folderTextField.setText(f
542: .getAbsolutePath());
543: }
544: } else if (e.getSource() == importPanel.moduleButton) {
545: ModuleSelector selector = new ModuleSelector();
546: CVSRoot root = CVSRoot.parse(repositoryStep
547: .getCvsRoot());
548: String path = selector.selectRepositoryPath(root);
549: if (path != null) {
550: if (!path.endsWith(module)) {
551: path += "/" + module;
552: }
553: importPanel.moduleTextField.setText(path);
554: }
555: }
556: }
557: }
558:
559: private static String checkInput(ImportPanel importPanel) {
560: boolean valid = true;
561:
562: File file = new File(importPanel.folderTextField.getText());
563: valid &= file.isDirectory();
564: if (!valid)
565: return NbBundle.getMessage(AddToRepositoryAction.class,
566: "BK0022");
567:
568: valid &= (new File(file, "CVS").exists()) == false; // NOI18N
569: if (!valid)
570: return NbBundle.getMessage(AddToRepositoryAction.class,
571: "BK0023");
572:
573: valid &= importPanel.commentTextArea.getText().trim().length() > 0;
574: if (!valid)
575: return NbBundle.getMessage(AddToRepositoryAction.class,
576: "BK0024");
577:
578: String module = importPanel.moduleTextField.getText().trim();
579: valid &= module.length() > 0;
580: if (!valid)
581: return NbBundle.getMessage(AddToRepositoryAction.class,
582: "BK0025");
583: valid &= module.indexOf(" ") == -1; // NOI18N // NOI18N
584: valid &= ".".equals(module.trim()) == false; // NOI18N
585: if (!valid)
586: return NbBundle.getMessage(AddToRepositoryAction.class,
587: "BK0026");
588:
589: return null;
590: }
591:
592: /**
593: * @return false on Thread.interrupted i.e. user cancel.
594: */
595: private boolean prepareIgnore(File dir) throws IOException {
596: File[] projectMeta = dir.listFiles();
597: Set ignored = new HashSet();
598: for (int i = 0; i < projectMeta.length; i++) {
599: if (Thread.interrupted()) {
600: return false;
601: }
602: File file = projectMeta[i];
603: String name = file.getName();
604: int sharability = SharabilityQuery.getSharability(file);
605: if (sharability == SharabilityQuery.NOT_SHARABLE) {
606: if (".cvsignore".equals(name) == false) { // NOI18N
607: ignored.add(name);
608: }
609: } else if (sharability == SharabilityQuery.MIXED) {
610: assert file.isDirectory() : file;
611: prepareIgnore(file);
612: }
613: }
614:
615: if (ignored.size() > 0) {
616: File cvsIgnore = new File(dir, ".cvsignore"); // NOI18N
617: OutputStream out = null;
618: try {
619: out = new FileOutputStream(cvsIgnore);
620: PrintWriter pw = new PrintWriter(out);
621: Iterator it = ignored.iterator();
622: while (it.hasNext()) {
623: String name = (String) it.next();
624: pw.println(name);
625: }
626: pw.close();
627: } finally {
628: if (out != null) {
629: try {
630: out.close();
631: } catch (IOException alreadyClosed) {
632: }
633: }
634: }
635: }
636: return true;
637: }
638:
639: protected boolean asynchronous() {
640: return false;
641: }
642:
643: }
|