KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > rubyproject > gems > GemManager


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.ruby.rubyproject.gems;
20
21 import java.awt.Cursor JavaDoc;
22 import java.awt.Dialog JavaDoc;
23 import java.awt.event.ActionEvent JavaDoc;
24 import java.awt.event.ActionListener JavaDoc;
25 import java.io.BufferedReader JavaDoc;
26 import java.io.File JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.io.InputStreamReader JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.Collections JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Map JavaDoc;
34 import javax.swing.JButton JavaDoc;
35 import javax.swing.JComponent JavaDoc;
36 import javax.swing.JTextArea JavaDoc;
37 import org.netbeans.api.progress.ProgressHandle;
38 import org.netbeans.modules.ruby.rubyproject.execution.ExecutionService;
39 import org.netbeans.modules.ruby.rubyproject.api.RubyInstallation;
40 import org.openide.DialogDescriptor;
41 import org.openide.DialogDisplayer;
42 import org.openide.ErrorManager;
43 import org.openide.util.HelpCtx;
44 import org.openide.util.NbBundle;
45 import org.openide.util.RequestProcessor;
46 import org.openide.util.Utilities;
47
48
49 /**
50  * Class which handles gem interactions - executing gem, installing, uninstalling, etc.
51  *
52  * @todo Use the new ExecutionService to do process management.
53  *
54  * @author Tor Norbye
55  */

56 public class GemManager {
57     /** Share over invocations of the dialog since these are slow to compute */
58     private static List JavaDoc<Gem> installed;
59
60     /** Share over invocations of the dialog since these are ESPECIALLY slow to compute */
61     private static List JavaDoc<Gem> available;
62
63     /** Creates a new instance of GemManager */
64     public GemManager() {
65     }
66
67     /** WARNING: slow call! Synchronous gem execution (unless refresh==false)! */
68     public List JavaDoc<Gem> getInstalledGems(boolean refresh, List JavaDoc<String JavaDoc> lines) {
69         if (refresh || (installed == null)) {
70             installed = new ArrayList JavaDoc<Gem>(40);
71             refreshList(installed, true, lines);
72         }
73
74         return installed;
75     }
76
77     public boolean haveGem() {
78         String JavaDoc gem = RubyInstallation.getInstance().getGem();
79
80         if (gem == null) {
81             return false;
82         }
83
84         return new File JavaDoc(gem).exists();
85     }
86
87     /** WARNING: slow call! Synchronous gem execution! */
88     public List JavaDoc<Gem> getAvailableGems(List JavaDoc<String JavaDoc> lines) {
89         if ((available == null) || (available.size() == 0)) {
90             available = new ArrayList JavaDoc<Gem>(300);
91             refreshList(available, false, lines);
92         }
93
94         return available;
95     }
96
97     private void refreshList(final List JavaDoc<Gem> list, final boolean local, final List JavaDoc<String JavaDoc> lines) {
98         list.clear();
99
100         // Install the given gem
101
List JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
102
103         if (local) {
104             argList.add("--local"); // NOI18N
105
} else {
106             argList.add("--remote"); // NOI18N
107
}
108
109         String JavaDoc[] args = (String JavaDoc[])argList.toArray(new String JavaDoc[argList.size()]);
110         boolean ok = gemRunner("list", null, false, null, null, null, null, null, lines, args);
111
112         if (ok) {
113             parseGemList(lines, list, local);
114
115             // Sort the list
116
Collections.sort(list);
117         }
118     }
119
120     private void parseGemList(List JavaDoc<String JavaDoc> lines, List JavaDoc<Gem> gemList, boolean setVersion) {
121         Gem gem = null;
122         boolean listStarted = false;
123
124         for (String JavaDoc line : lines) {
125             if (line.length() == 0) {
126                 gem = null;
127
128                 continue;
129             }
130
131             if (line.startsWith("*** ")) {
132                 listStarted = true;
133                 gem = null;
134
135                 continue;
136             }
137
138             if (!listStarted) {
139                 // Skip status messages etc.
140
continue;
141             }
142
143             if (Character.isWhitespace(line.charAt(0))) {
144                 if (gem != null) {
145                     String JavaDoc description = line.trim();
146
147                     if (gem.desc == null) {
148                         gem.desc = description;
149                     } else {
150                         gem.desc = gem.desc + " " + description;
151                     }
152                 }
153             } else {
154                 if (line.charAt(0) == '.') {
155                     continue;
156                 }
157
158                 // Should be a gem
159
int versionIndex = line.indexOf('(');
160
161                 if (versionIndex != -1) {
162                     String JavaDoc name = line.substring(0, versionIndex).trim();
163                     int endIndex = line.indexOf(')');
164                     String JavaDoc versions;
165
166                     if (endIndex != -1) {
167                         versions = line.substring(versionIndex + 1, endIndex);
168                     } else {
169                         versions = line.substring(versionIndex);
170                     }
171
172                     String JavaDoc version = setVersion ? versions : null;
173                     gem = new Gem(name, versions, version);
174                     gemList.add(gem);
175                 } else {
176                     gem = new Gem(line.trim(), "", null);
177                     gemList.add(gem);
178                 }
179             }
180         }
181     }
182
183     /** Non-blocking gem executor which also provides progress UI etc. */
184     private void asynchGemRunner(final JComponent JavaDoc parent, final String JavaDoc description,
185         final String JavaDoc successMessage, final String JavaDoc failureMessage, final List JavaDoc<String JavaDoc> lines,
186         final Runnable JavaDoc successCompletionTask, final String JavaDoc command, final String JavaDoc... commandArgs) {
187         final Cursor JavaDoc originalCursor = parent.getCursor();
188         Cursor JavaDoc busy = Utilities.createProgressCursor(parent);
189         parent.setCursor(busy);
190
191         final ProgressHandle progressHandle = null;
192         final boolean interactive = true;
193         final JButton JavaDoc closeButton = new JButton JavaDoc(NbBundle.getMessage(GemManager.class, "CTL_Close"));
194         closeButton.getAccessibleContext()
195                    .setAccessibleDescription(NbBundle.getMessage(GemManager.class, "AD_Close"));
196
197         Object JavaDoc[] options = new Object JavaDoc[] { closeButton, DialogDescriptor.CANCEL_OPTION };
198         closeButton.setEnabled(false);
199
200         final GemProgressPanel progress =
201             new GemProgressPanel(NbBundle.getMessage(GemManager.class, "GemPleaseWait"));
202         DialogDescriptor descriptor =
203             new DialogDescriptor(progress, description, true, options, closeButton,
204                 DialogDescriptor.DEFAULT_ALIGN, new HelpCtx(InstalledGemsPanel.class), null); // NOI18N
205
descriptor.setModal(true);
206
207         final Process JavaDoc[] processHolder = new Process JavaDoc[1];
208         final Dialog JavaDoc dlg = DialogDisplayer.getDefault().createDialog(descriptor);
209
210         closeButton.addActionListener(new ActionListener JavaDoc() {
211                 public void actionPerformed(ActionEvent JavaDoc ev) {
212                     dlg.setVisible(false);
213                     dlg.dispose();
214                     parent.setCursor(originalCursor);
215                 }
216             });
217
218         Runnable JavaDoc runner =
219             new Runnable JavaDoc() {
220                 public void run() {
221                     try {
222                         JTextArea JavaDoc textArea = progress.getOutputArea();
223                         boolean succeeded =
224                             gemRunner(command, progressHandle, interactive, description,
225                                 successMessage, failureMessage, textArea, processHolder, lines,
226                                 commandArgs);
227
228                         closeButton.setEnabled(true);
229
230                         progress.done(succeeded ? successMessage : failureMessage);
231
232                         if (succeeded && (successCompletionTask != null)) {
233                             successCompletionTask.run();
234                         }
235                     } finally {
236                         parent.setCursor(originalCursor);
237                     }
238                 }
239             };
240
241         RequestProcessor.getDefault().post(runner, 50);
242
243         dlg.setVisible(true);
244
245         if (descriptor.getValue() == DialogDescriptor.CANCEL_OPTION) {
246             parent.setCursor(originalCursor);
247
248             Process JavaDoc process = processHolder[0];
249
250             if (process != null) {
251                 process.destroy();
252                 dlg.setVisible(false);
253                 dlg.dispose();
254             }
255         }
256     }
257
258     private boolean gemRunner(String JavaDoc command, ProgressHandle progressHandle, boolean interactive,
259         String JavaDoc description, String JavaDoc successMessage, String JavaDoc failureMessage, JTextArea JavaDoc textArea,
260         Process JavaDoc[] processHolder, List JavaDoc<String JavaDoc> lines, String JavaDoc... commandArgs) {
261         // Install the given gem
262
String JavaDoc gemCmd = RubyInstallation.getInstance().getGem();
263         List JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
264
265         argList.add(gemCmd);
266         argList.add(command);
267
268         for (String JavaDoc arg : commandArgs) {
269             argList.add(arg);
270         }
271
272         String JavaDoc[] args = (String JavaDoc[])argList.toArray(new String JavaDoc[argList.size()]);
273         ProcessBuilder JavaDoc pb = new ProcessBuilder JavaDoc(args);
274         pb.redirectErrorStream();
275
276         // PATH additions for JRuby etc.
277
String JavaDoc binPath = new File JavaDoc(gemCmd).getParent();
278         ExecutionService.setupEnvironment(pb, binPath);
279
280         // Proxy
281
String JavaDoc proxy = getNetbeansHttpProxy();
282
283         if (proxy != null) {
284             // This unfortunately does not work -- gems blows up. Looks like
285
// a RubyGems bug.
286
// ERROR: While executing gem ... (NoMethodError)
287
// undefined method `[]=' for #<Gem::ConfigFile:0xb6c763 @hash={} ,@args=["--remote", "-p", "http://foo.bar:8080"] ,@config_file_name=nil ,@verbose=true>
288
//argList.add("--http-proxy"); // NOI18N
289
//argList.add(proxy);
290
// (If you uncomment the above, move it up above the args = argList.toArray line)
291
//
292
// Running gems list -p or --http-proxy triggers this so for now
293
// work around with environment variables instead - which still work
294
Map JavaDoc<String JavaDoc, String JavaDoc> env = pb.environment();
295
296             if ((env.get("HTTP_PROXY") == null) && (env.get("http_proxy") == null)) { // NOI18N
297
env.put("HTTP_PROXY", proxy);
298             }
299
300             // PENDING - what if proxy was null so the user has TURNED off proxies while
301
// there is still an environment variable set - should we honor their
302
// environment, or honor their NetBeans proxy settings (e.g. unset HTTP_PROXY
303
// in the environment before launching gem?
304
}
305
306         if (lines == null) {
307             lines = new ArrayList JavaDoc(40);
308         }
309
310         int exitCode = -1;
311
312         try {
313             Process JavaDoc process = pb.start();
314
315             if (processHolder != null) {
316                 processHolder[0] = process;
317             }
318
319             InputStream JavaDoc is = process.getInputStream();
320             InputStreamReader JavaDoc isr = new InputStreamReader JavaDoc(is);
321             BufferedReader JavaDoc br = new BufferedReader JavaDoc(isr);
322             String JavaDoc line;
323
324             try {
325                 while (true) {
326                     line = br.readLine();
327
328                     if (line == null) {
329                         break;
330                     }
331
332                     if (textArea != null) {
333                         textArea.append(line + "\n");
334                     }
335
336                     lines.add(line);
337                 }
338             } catch (IOException JavaDoc ioe) {
339                 // When we cancel we call Process.destroy which may quite possibly
340
// raise an IO Exception in this thread reading text out of the
341
// process. Silently ignore that.
342
String JavaDoc message = "*** Gem Process Killed ***\n";
343                 lines.add(message);
344
345                 if (textArea != null) {
346                     textArea.append(message);
347                 }
348             }
349
350             exitCode = process.waitFor();
351
352             if (exitCode != 0) {
353                 try {
354                     // This shouldn't be necessary since I call
355
// ProcessBuilder.redirectErrorStream(), but
356
// it doesn't appear to work (at least on OSX)
357
// so I can read out additional info here
358
is = process.getErrorStream();
359                     isr = new InputStreamReader JavaDoc(is);
360                     br = new BufferedReader JavaDoc(isr);
361
362                     while ((line = br.readLine()) != null) {
363                         if (textArea != null) {
364                             textArea.append(line + "\n");
365                         }
366
367                         lines.add(line);
368                     }
369                 } catch (IOException JavaDoc ioe) {
370                     // When we cancel we call Process.destroy which may quite possibly
371
// raise an IO Exception in this thread reading text out of the
372
// process. Silently ignore that.
373
String JavaDoc message = "*** Gem Process Killed ***\n";
374                     lines.add(message);
375
376                     if (textArea != null) {
377                         textArea.append(message);
378                     }
379                 }
380             }
381         } catch (IOException JavaDoc ex) {
382             ErrorManager.getDefault().notify(ex);
383         } catch (InterruptedException JavaDoc ex) {
384             ErrorManager.getDefault().notify(ex);
385         }
386
387         boolean succeeded = exitCode == 0;
388
389         return succeeded;
390     }
391
392     /**
393      * Install the given gem.
394      *
395      * @param gem Gem description for the gem to be installed. Only the name is relevant.
396      * @param parent For asynchronous tasks, provide a parent JComponent that will have progress dialogs added,
397      * a possible cursor change, etc.
398      * @param progressHandle If the task is not asynchronous, use the given handle for progress notification.
399      * @param asynchronous If true, run the gem task asynchronously - returning immediately and running the gem task
400      * in a background thread. A progress bar and message will be displayed (along with the option to view the
401      * gem output). If the exit code is normal, the completion task will be run at the end.
402      * @param asyncCompletionTask If asynchronous is true and the gem task completes normally, this task will be run at the end.
403      * @param rdoc If true, generate rdoc as part of the installation
404      * @param ri If true, generate ri data as part of the installation
405      * @param version If non null, install the specified version rather than the latest available version
406      */

407     public boolean install(Gem[] gems, JComponent JavaDoc parent, ProgressHandle progressHandle, boolean rdoc,
408         boolean ri, String JavaDoc version, boolean includeDeps, boolean asynchronous,
409         Runnable JavaDoc asyncCompletionTask) {
410         // Install the given gem
411
List JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
412         
413         for (Gem gem : gems) {
414             argList.add(gem.getName());
415         }
416
417         //argList.add("--verbose"); // NOI18N
418
if (!rdoc) {
419             argList.add("--no-rdoc"); // NOI18N
420
}
421
422         if (!ri) {
423             argList.add("--no-ri"); // NOI18N
424
}
425
426         if (includeDeps) {
427             argList.add("--include-dependencies"); // NOI18N
428
} else {
429             argList.add("--ignore-dependencies"); // NOI18N
430
}
431
432         argList.add("--version"); // NOI18N
433

434         if ((version != null) && (version.length() > 0)) {
435             argList.add(version);
436         } else {
437             argList.add("> 0"); // NOI18N
438
}
439
440         String JavaDoc[] args = (String JavaDoc[])argList.toArray(new String JavaDoc[argList.size()]);
441
442         String JavaDoc title = NbBundle.getMessage(GemManager.class, "Installation");
443         String JavaDoc success = NbBundle.getMessage(GemManager.class, "InstallationOk");
444         String JavaDoc failure = NbBundle.getMessage(GemManager.class, "InstallationFailed");
445         String JavaDoc gemCmd = "install"; // NOI18N
446

447         if (asynchronous) {
448             asynchGemRunner(parent, title, success, failure, null, asyncCompletionTask, gemCmd, args);
449
450             return false;
451         } else {
452             boolean ok =
453                 gemRunner(gemCmd, progressHandle, true, title, success, failure, null, null, null,
454                     args);
455
456             return ok;
457         }
458     }
459
460     /**
461      * Uninstall the given gem.
462      *
463      * @param gem Gem description for the gem to be uninstalled. Only the name is relevant.
464      * @param parent For asynchronous tasks, provide a parent JComponent that will have progress dialogs added,
465      * a possible cursor change, etc.
466      * @param progressHandle If the task is not asynchronous, use the given handle for progress notification.
467      * @param asynchronous If true, run the gem task asynchronously - returning immediately and running the gem task
468      * in a background thread. A progress bar and message will be displayed (along with the option to view the
469      * gem output). If the exit code is normal, the completion task will be run at the end.
470      * @param asyncCompletionTask If asynchronous is true and the gem task completes normally, this task will be run at the end.
471      */

472     public boolean uninstall(Gem[] gems, JComponent JavaDoc parent, ProgressHandle progressHandle,
473         boolean asynchronous, Runnable JavaDoc asyncCompletionTask) {
474         // Install the given gem
475
List JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
476
477         for (Gem gem : gems) {
478             argList.add(gem.getName());
479         }
480
481         //argList.add("--verbose"); // NOI18N
482
argList.add("--all"); // NOI18N
483
argList.add("--executables"); // NOI18N
484
argList.add("--ignore-dependencies"); // NOI18N
485

486         String JavaDoc[] args = (String JavaDoc[])argList.toArray(new String JavaDoc[argList.size()]);
487         String JavaDoc title = NbBundle.getMessage(GemManager.class, "Uninstallation");
488         String JavaDoc success = NbBundle.getMessage(GemManager.class, "UninstallationOk");
489         String JavaDoc failure = NbBundle.getMessage(GemManager.class, "UninstallationFailed");
490         String JavaDoc gemCmd = "uninstall"; // NOI18N
491

492         if (asynchronous) {
493             asynchGemRunner(parent, title, success, failure, null, asyncCompletionTask, gemCmd, args);
494
495             return false;
496         } else {
497             boolean ok =
498                 gemRunner(gemCmd, progressHandle, true, title, success, failure, null, null, null,
499                     args);
500
501             return ok;
502         }
503     }
504
505     /**
506      * Update the given gem, or all gems if gem == null
507      *
508      * @param gem Gem description for the gem to be uninstalled. Only the name is relevant. If null, all installed gems
509      * will be updated.
510      * @param parent For asynchronous tasks, provide a parent JComponent that will have progress dialogs added,
511      * a possible cursor change, etc.
512      * @param progressHandle If the task is not asynchronous, use the given handle for progress notification.
513      * @param asynchronous If true, run the gem task asynchronously - returning immediately and running the gem task
514      * in a background thread. A progress bar and message will be displayed (along with the option to view the
515      * gem output). If the exit code is normal, the completion task will be run at the end.
516      * @param asyncCompletionTask If asynchronous is true and the gem task completes normally, this task will be run at the end.
517      */

518     public boolean update(Gem[] gems, JComponent JavaDoc parent, ProgressHandle progressHandle, boolean rdoc,
519         boolean ri, boolean asynchronous, Runnable JavaDoc asyncCompletionTask) {
520         // Install the given gem
521
List JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
522
523         if (gems != null) {
524             for (Gem gem : gems) {
525                 argList.add(gem.getName());
526             }
527         }
528
529         argList.add("--verbose"); // NOI18N
530

531         if (!rdoc) {
532             argList.add("--no-rdoc"); // NOI18N
533
}
534
535         if (!ri) {
536             argList.add("--no-ri"); // NOI18N
537
}
538
539         argList.add("--include-dependencies"); // NOI18N
540

541         String JavaDoc[] args = (String JavaDoc[])argList.toArray(new String JavaDoc[argList.size()]);
542
543         String JavaDoc title = NbBundle.getMessage(GemManager.class, "Update");
544         String JavaDoc success = NbBundle.getMessage(GemManager.class, "UpdateOk");
545         String JavaDoc failure = NbBundle.getMessage(GemManager.class, "UpdateFailed");
546         String JavaDoc gemCmd = "update"; // NOI18N
547

548         if (asynchronous) {
549             asynchGemRunner(parent, title, success, failure, null, asyncCompletionTask, gemCmd, args);
550
551             return false;
552         } else {
553             boolean ok =
554                 gemRunner(gemCmd, progressHandle, true, title, success, failure, null, null, null,
555                     args);
556
557             return ok;
558         }
559     }
560
561     /**
562       * Reads property detected by native launcher (core/launcher).
563       * Implemented for Windows and GNOME.
564       * This was copied from "detectNetbeansHttpProxy in subversion/** /ProxyDescriptor.java.
565       */

566     private static String JavaDoc getNetbeansHttpProxy() {
567         String JavaDoc host = System.getProperty("http.proxyHost"); // NOI18N
568

569         if (host == null) {
570             return null;
571         }
572
573         String JavaDoc portHttp = System.getProperty("http.proxyPort"); // NOI18N
574
int port;
575
576         try {
577             port = Integer.parseInt(portHttp);
578         } catch (NumberFormatException JavaDoc e) {
579             port = 8080;
580         }
581
582         // Gem requires "http://" in front of the port name if it's not already there
583
if (host.indexOf(':') == -1) {
584             host = "http://" + host; // NOI18N
585
}
586
587         return host + ":" + port;
588     }
589
590     public static class Gem implements Comparable JavaDoc<Gem> {
591         private String JavaDoc name;
592         private String JavaDoc desc;
593         private String JavaDoc version;
594         private String JavaDoc availableVersions;
595
596         public Gem(String JavaDoc name, String JavaDoc versions, String JavaDoc version) {
597             this.name = name;
598             this.version = version;
599             this.availableVersions = versions;
600         }
601
602         public String JavaDoc getName() {
603             return name;
604         }
605
606         public String JavaDoc getVersions() {
607             return availableVersions;
608         }
609
610         public String JavaDoc getDescription() {
611             return desc;
612         }
613
614         public String JavaDoc toString() {
615             // Shown in ListCellRenderer etc.
616
StringBuilder JavaDoc sb = new StringBuilder JavaDoc(100);
617             sb.append("<html><b>");
618             sb.append(name);
619             sb.append("</b>");
620
621             if (version != null) {
622                 sb.append(" (");
623                 sb.append(version);
624                 sb.append(") ");
625             }
626
627             if (desc != null) {
628                 sb.append(": ");
629                 sb.append(desc);
630             }
631
632             sb.append("</html>");
633
634             return sb.toString();
635         }
636
637         public int compareTo(Gem other) {
638             return name.compareTo(other.name);
639         }
640     }
641 }
642
Popular Tags