KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > src > nodes > SourceEditSupport


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
20 package org.openide.src.nodes;
21
22
23 import java.awt.Component JavaDoc;
24 import java.awt.datatransfer.Transferable JavaDoc;
25 import java.beans.*;
26 import java.io.IOException JavaDoc;
27 import java.lang.reflect.Modifier JavaDoc;
28 import java.util.*;
29 import org.netbeans.api.java.classpath.ClassPath;
30
31 import org.openide.*;
32 import org.openide.src.*;
33 import org.openide.nodes.*;
34 import org.openide.cookies.SourceCookie;
35 import org.openide.filesystems.FileObject;
36 import org.openide.loaders.DataObject;
37 import org.openide.util.*;
38 import org.openide.util.datatransfer.NewType;
39 import org.openide.util.datatransfer.PasteType;
40 import org.openide.util.datatransfer.ExTransferable;
41
42 /** This class defines utilities for editing source using hierarchy API,
43 * e.g. creation new types for class elements, runAtomicAsUser support, ...
44 *
45 * @author Petr Hamernik
46 */

47 class SourceEditSupport {
48
49     static final ResourceBundle bundle = NbBundle.getBundle(SourceEditSupport.class);
50
51     static final String JavaDoc[] MENU_NAMES = {
52         bundle.getString("MENU_CREATE_BLOCK"), bundle.getString("MENU_CREATE_VARIABLE"),
53         bundle.getString("MENU_CREATE_CONSTRUCTOR"), bundle.getString("MENU_CREATE_METHOD"),
54         bundle.getString("MENU_CREATE_CLASS"), bundle.getString("MENU_CREATE_INTERFACE")
55     };
56
57     /* Get the new types that can be created in this node.
58     * For example, a node representing a Java package will permit classes to be added.
59     * @return array of new type operations that are allowed
60     */

61     public static NewType[] createNewTypes(ClassElement element) {
62         if (element.isClass()) {
63             // class new types
64
return new NewType[] {
65                        new ElementNewType(element, (byte) 0),
66                        new ElementNewType(element, (byte) 1),
67                        new ElementNewType(element, (byte) 2),
68                        new ElementNewType(element, (byte) 3),
69                        new ElementNewType(element, (byte) 4),
70                        new ElementNewType(element, (byte) 5)
71                    };
72         }
73         else {
74             // interface new types
75
return new NewType[] {
76                        new ElementNewType(element, (byte) 1),
77                        new ElementNewType(element, (byte) 3),
78                        new ElementNewType(element, (byte) 4),
79                        new ElementNewType(element, (byte) 5)
80                    };
81         }
82     }
83
84     /** New types for class element */
85     static class ElementNewType extends NewType {
86         /** Class element where to create new element */
87         ClassElement element;
88
89         /** Filled in if the target class is an interface to fool customizers and
90          * have the restrictions on them.
91          */

92         ClassElement proxy;
93         
94         /** The kind of element to create */
95         byte kind;
96         
97         /** Creates new type
98         * @param element Where to create new element.
99         * @param kind The kind of the element to create
100         */

101         public ElementNewType(ClassElement element, byte kind) {
102             this.element = element;
103             proxy = new ClassElement();
104             try {
105                 proxy.setName(Identifier.create("Default")); // NOI18N
106
proxy.setClassOrInterface(element.isClassOrInterface());
107             } catch (SourceException ex) {
108                     // it can NOT happen
109
}
110             this.kind = kind;
111         }
112
113         /** Get the name of the new type.
114         * @return localized name.
115         */

116         public String JavaDoc getName() {
117             return MENU_NAMES[kind];
118         }
119
120         /** Help context */
121         public org.openide.util.HelpCtx getHelpCtx() {
122             return new org.openide.util.HelpCtx (SourceEditSupport.class.getName () + ".newElement" + kind); // NOI18N
123
}
124
125         /** Creates new element */
126         public void create () throws IOException JavaDoc {
127             final Identifier outerName = element.getName();
128             final boolean outerIsClass = element.isClass();
129
130             Element newElement = null;
131
132             try {
133                 switch (kind) {
134                 case 0:
135                     {
136                         // Adding initializer
137
InitializerElement e = new InitializerElement();
138                         e.setStatic(true);
139                         e.setBody("\n"); // NOI18N
140
newElement = e;
141                         break;
142                     }
143                 case 1:
144                     {
145                         // Adding field
146
FieldElement e = new FieldElement();
147                         e.setType(Type.INT);
148                         e.setName(Identifier.create("newField")); // NOI18N
149
e.setModifiers(Modifier.PRIVATE + (outerIsClass ? 0 : Modifier.STATIC));
150                         proxy.addField(e);
151                         e = proxy.getFields()[0];
152                         FieldCustomizer cust = new FieldCustomizer(e);
153                         if (openCustomizer(cust, "TIT_NewField") && cust.isOK()) // NOI18N
154
newElement = e;
155                         break;
156                     }
157                 case 2:
158                     {
159                         // Adding constructor
160
ConstructorElement e = new ConstructorElement();
161                         e.setName(Identifier.create(((ClassElement)element).getName().getName()));
162                         e.setModifiers(Modifier.PUBLIC);
163                         e.setBody("\n"); // NOI18N
164
MethodCustomizer cust = new MethodCustomizer(e);
165                         if (openCustomizer(cust, "TIT_NewConstructor") && cust.isOK()) // NOI18N
166
newElement = e;
167                         break;
168                     }
169                 case 3:
170                     {
171                         // Adding method
172
MethodElement e = new MethodElement();
173                         e.setReturn(Type.VOID);
174                         e.setName(Identifier.create("newMethod")); // NOI18N
175
e.setModifiers(Modifier.PUBLIC);
176                         e.setBody(outerIsClass ? "\n" : null); // NOI18N
177
proxy.addMethod(e);
178                         e = proxy.getMethods()[0];
179                         MethodCustomizer cust = new MethodCustomizer(e);
180                         if (openCustomizer(cust, "TIT_NewMethod") && cust.isOK()) {// NOI18N
181
if ((e.getModifiers() & (Modifier.ABSTRACT | Modifier.NATIVE)) > 0) {
182                 // abstract and native methods cannot have bodies.
183
e.setBody(null);
184                 }
185                             newElement = e;
186             }
187                         break;
188                     }
189                 case 4:
190                     {
191                         // Adding inner class
192
ClassElement e = new ClassElement();
193                         e.setName(Identifier.create(outerName.getFullName() + ".InnerClass", "InnerClass")); // NOI18N
194
e.setModifiers(Modifier.PUBLIC);
195                         e.setClassOrInterface(true);
196                         proxy.addClass(e);
197                         e = proxy.getClasses()[0];
198                         ClassCustomizer cust = new ClassCustomizer(e);
199                         if (openCustomizer(cust, "TIT_NewClass") && cust.isOK()) // NOI18N
200
newElement = e;
201                         break;
202                     }
203                 case 5:
204                     {
205                         // Adding inner interface
206
ClassElement e = new ClassElement();
207                         e.setName(Identifier.create(outerName.getFullName() + ".InnerInterface", "InnerInterface")); // NOI18N
208
e.setModifiers(Modifier.PUBLIC);
209                         e.setClassOrInterface(false);
210                         proxy.addClass(e);
211                         e = proxy.getClasses()[0];
212                         ClassCustomizer cust = new ClassCustomizer(e);
213                         if (openCustomizer(cust, "TIT_NewInterface") && cust.isOK()) // NOI18N
214
newElement = e;
215                         break;
216                     }
217                 }
218             }
219             catch (SourceException exc) {
220                 // shouldn't happen - memory implementation
221
// is not based on java source.
222
}
223
224             if (newElement == null)
225                 return;
226
227             final Element addingElement = newElement;
228             SourceEditSupport.invokeAtomicAsUser(element, new SourceEditSupport.ExceptionalRunnable() {
229                                                      public void run() throws SourceException {
230                                                          switch (kind) {
231                                                          case 0:
232                                                              ((ClassElement)element).addInitializer((InitializerElement)addingElement);
233                                                              return;
234                                                          case 1:
235                                                              ((ClassElement)element).addField((FieldElement)addingElement);
236                                                              return;
237                                                          case 2:
238                                                              ((ClassElement)element).addConstructor((ConstructorElement)addingElement);
239                                                              return;
240                                                          case 3:
241                                                              ((ClassElement)element).addMethod((MethodElement)addingElement);
242                                                              return;
243                                                          case 4:
244                                                          case 5:
245                                                              element.addClass((ClassElement)addingElement);
246                                                              return;
247                                                          }
248                                                      }
249                                                  });
250         }
251     }
252
253     /** Show dialog and allow user to modify new element.
254     * @param customizer The component to be displayed
255     * @param titleKey the key to resource bundle for the title of dialog
256     * @return <CODE>true</CODE> if user pressed OK button,
257     * otherwise <CODE>false</CODE> (for CANCEL)
258     */

259     static boolean openCustomizer(Component JavaDoc customizer, String JavaDoc titleKey) {
260         NotifyDescriptor desriptor = new NotifyDescriptor(
261                                          customizer,
262                                          ElementNode.bundle.getString(titleKey),
263                                          NotifyDescriptor.OK_CANCEL_OPTION,
264                                          NotifyDescriptor.PLAIN_MESSAGE,
265                                          null, null);
266
267         Object JavaDoc ret = DialogDisplayer.getDefault().notify(desriptor);
268         return (ret == NotifyDescriptor.OK_OPTION);
269     }
270
271     /** Invokes the runnable using SourceElement.runAtomicAsUser, if it can find
272     * a source element for the given hierarchy element. If the SourceElement can't
273     * be found, the runnable is executed without any protection.
274     * @exception IOException If SourceException occured inside the runnable.
275     */

276     static void invokeAtomicAsUser(Element element, final ExceptionalRunnable exRun) throws IOException JavaDoc {
277         try {
278             runAsUser(element, exRun);
279         } catch (SourceException.IO e) {
280             ErrorManager.getDefault().annotate(e.getReason(),
281                 ErrorManager.USER, null, null, null, null);
282             throw e.getReason();
283                        }
284                        catch (SourceException e) {
285             if (Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N
286
e.printStackTrace();
287             IOException JavaDoc x = new IOException JavaDoc(e.getMessage());
288             // #9512 -- this exception is expected, so lower its priority to "user".
289
ErrorManager.getDefault().annotate(x,
290                 ErrorManager.USER, null, null, e, null);
291             throw x;
292         }
293     }
294     
295     static void runAsUser(Element ref, final ExceptionalRunnable exRun) throws SourceException {
296         final SourceException ex[] = { null };
297         SourceElement src = findSource(ref);
298         boolean retry = false;
299         do {
300             ex[0] = null;
301         if (src == null) {
302             exRun.run();
303         } else {
304             src.runAtomicAsUser(new Runnable JavaDoc() {
305                 public void run() {
306                     try {
307                         exRun.run();
308                     } catch (SourceException e) {
309                         ex[0] = e;
310                     }
311                 }
312             });
313         }
314             if (ex[0] != null) {
315                 if (retry)
316                     break;
317                 retry = true;
318                 if (ex[0] instanceof SourceException.IO) {
319                     IOException JavaDoc iex = ((SourceException.IO)ex[0]).getReason();
320                     if (iex instanceof UserQuestionException) {
321                         UserQuestionException uex = (UserQuestionException)iex;
322                         NotifyDescriptor.Confirmation nc = new NotifyDescriptor.Confirmation(uex.getLocalizedMessage(),
323                                 bundle.getString("TIT_CannotWriteFile"), NotifyDescriptor.Confirmation.YES_NO_OPTION);
324                         Object JavaDoc o = DialogDisplayer.getDefault().notify(nc);
325                         if (o == NotifyDescriptor.YES_OPTION) {
326                             try {
327                                 uex.confirmed();
328                                 // HACK!! See issue #23407
329
continue;
330                             } catch (IOException JavaDoc x) {
331                                 ex[0] = new SourceException.IO(x);
332                                 ErrorManager.getDefault().annotate(ex[0], ErrorManager.USER, null, null, x, null);
333                             }
334                         } else {
335                             iex = new IOException JavaDoc("Cannot write"); // NOI18N
336
ErrorManager.getDefault().annotate(iex, ErrorManager.USER, null, bundle.getString("ERR_CannotWriteFile"), uex, null);
337                             ex[0] = new SourceException.IO(iex);
338                             ErrorManager.getDefault().annotate(ex[0], ErrorManager.USER, null, bundle.getString("ERR_CannotWriteFile"), iex, null);
339                         }
340                     }
341                 }
342                 // not a UserQuestionException, unconfirmed UQE, exception from UQE.confirmed.
343
break;
344             }
345         } while (ex[0] != null);
346
347         if (ex[0] != null)
348             throw ex[0];
349     }
350
351     /** This interface is used like runnable, but its method run
352     * could throw BadLocationException.
353     * @exception SourceException
354     */

355     static interface ExceptionalRunnable {
356         public void run() throws SourceException;
357     }
358
359     static boolean isWriteable(Element element) {
360         SourceElement el = findSource(element);
361     DataObject d = el == null ? null : (DataObject)el.getCookie(DataObject.class);
362     if (d == null) {
363         return true;
364     }
365     return !d.getPrimaryFile().isReadOnly();
366     }
367
368     /** Find the source for the specifier element.
369     * @return instance of SourceElement or null, if the source can't be found.
370     */

371     static SourceElement findSource(Element element) {
372         SourceElement source = null;
373         ClassElement clazz = null;
374         if (element instanceof ClassElement) {
375             clazz = (ClassElement) element;
376         }
377         else if (element instanceof MemberElement) {
378             clazz = ((MemberElement) element).getDeclaringClass();
379         }
380         else if (element instanceof InitializerElement) {
381             clazz = ((InitializerElement) element).getDeclaringClass();
382         }
383         else if (element instanceof SourceElement) {
384             return (SourceElement) element;
385         }
386         if (clazz != null) {
387             source = clazz.getSource();
388         }
389         return source;
390     }
391     
392     static void createJavaFile(ClassElement clazz, FileObject target) throws SourceException, IOException JavaDoc {
393         DataObject targetObject;
394         String JavaDoc name = clazz.getName().getSourceName();
395         FileObject newFile;
396         ClassPath cp = ClassPath.getClassPath(target, ClassPath.SOURCE);
397         if (cp == null) {
398             throw new IOException JavaDoc("No known package source root for " + target); // NOI18N
399
}
400     String JavaDoc packageName = cp.getResourceName(target, '.', true);
401         String JavaDoc newName;
402         
403     if ("".equals(packageName)) // NOI18N
404
packageName = null;
405         newName = org.openide.filesystems.FileUtil.findFreeFileName(target, name, "java"); // NOI18N
406
newFile = target.createData(name, "java"); // NOI18N
407
SourceElement newSrc;
408         SourceCookie cookie;
409         try {
410             targetObject = DataObject.find(newFile);
411         } catch (org.openide.loaders.DataObjectNotFoundException e) {
412             throw (IOException JavaDoc)ErrorManager.getDefault().annotate(
413                 new IOException JavaDoc(e.getMessage()),
414                 ErrorManager.EXCEPTION, "Data object can't be created", // NOI18N
415
bundle.getString("EXC_CREATE_SOURCE_FILE"),
416                 e, null
417             );
418         }
419         cookie = (SourceCookie)targetObject.getCookie(SourceCookie.class);
420         if (cookie == null) {
421             // perhaps java sources not installed ?
422
throw (SourceException)ErrorManager.getDefault().annotate(
423                 new SourceException("Source element cannot be found"), // NOI18N
424
bundle.getString("EXC_CREATE_SOURCE_FILE")
425             );
426         }
427         if (packageName != null) // NOI18N
428
cookie.getSource().setPackage(Identifier.create(packageName));
429         cookie.getSource().addClass(clazz);
430     
431     ClassElement targetC = cookie.getSource().getClass(Identifier.create(clazz.getName().getSourceName()));
432     int mods = targetC.getModifiers() & ~Modifier.STATIC;
433     if ((mods & (Modifier.PROTECTED | Modifier.PRIVATE)) > 0) {
434         mods = (mods & ~(Modifier.PROTECTED | Modifier.PRIVATE)) | Modifier.PUBLIC;
435     }
436     targetC.setModifiers(mods);
437     }
438     
439     static void removeClass(ClassElement clazz) throws SourceException {
440         if (clazz.getDeclaringClass() != null) {
441             clazz.getDeclaringClass().removeClass(clazz);
442         } else {
443             SourceElement src = SourceEditSupport.findSource(clazz);
444             if (src == null) {
445                 throw (SourceException)ErrorManager.getDefault().annotate(
446                     new SourceException("Element has no source"), // NOI18N
447
bundle.getString("EXC_NO_SOURCE")
448                 );
449             }
450             src.removeClass(clazz);
451         }
452     }
453
454     /* default */static class PackagePaste implements NodeTransfer.Paste {
455         private static PasteType[] EMPTY_TYPES = new PasteType[0];
456         /** True, if the paste should remove the original class element.
457         */

458         private boolean deleteSelf;
459
460         /** Class element to paste.
461         */

462         private ClassElement element;
463         
464         PackagePaste(ClassElement cls, boolean deleteSelf) {
465             this.deleteSelf = deleteSelf;
466             this.element = cls;
467         }
468         
469         public PasteType[] types(Node target) {
470             DataObject obj = (DataObject)target.getCookie(DataObject.class);
471             if (element == null || obj == null)
472                 return EMPTY_TYPES;
473             FileObject fob = obj.getPrimaryFile();
474             if (!fob.isFolder()) {
475                 return EMPTY_TYPES;
476             }
477             return new PasteType[] {
478                 new Type(fob)
479             };
480         }
481
482         private class Type extends PasteType {
483             /** Target file folder
484             */

485             private FileObject target;
486
487             Type(FileObject target) {
488                 this.target = target;
489             }
490
491             public String JavaDoc getName() {
492                 return bundle.getString("MENU_PASTE_AS_FILE");
493             }
494
495             public org.openide.util.HelpCtx getHelpCtx() {
496                 return super.getHelpCtx();
497             }
498
499             public Transferable JavaDoc paste() throws IOException JavaDoc {
500                 final ClassElement clazz = PackagePaste.this.element;
501                 final boolean del = PackagePaste.this.deleteSelf;
502
503         try {
504                     createJavaFile(clazz, target);
505         } catch (SourceException ex) {
506                 IOException JavaDoc x = new IOException JavaDoc(ex.getMessage());
507                 ErrorManager.getDefault().annotate(x, ex);
508             throw x;
509         }
510                 if (del) {
511                     final SourceException ex[] = { null };
512                     SourceEditSupport.invokeAtomicAsUser(clazz, new SourceEditSupport.ExceptionalRunnable() {
513                         public void run() throws SourceException {
514                             try {
515                                     removeClass(clazz);
516                             } catch (SourceException e) {
517                                 ex[0] = e;
518                             }
519                         }
520                     });
521                     if (ex[0] != null) {
522                 IOException JavaDoc x = new IOException JavaDoc(ex[0].getMessage());
523                 ErrorManager.getDefault().annotate(x, ex[0]);
524                         throw x;
525                     }
526                     PackagePaste.this.element = null;
527                     return ExTransferable.EMPTY;
528                 } else {
529                     return null;
530                 }
531             }
532         }
533     }
534     
535     static class ClassMultiPasteType extends PasteType {
536         ClassElementNode target;
537         Collection members;
538         boolean delete;
539
540         ClassMultiPasteType(ClassElementNode target, Collection members, boolean delete) {
541             this.target = target;
542             this.members = members;
543             this.delete = delete;
544         }
545
546         public Transferable JavaDoc paste() throws IOException JavaDoc {
547             for (Iterator it = members.iterator(); it.hasNext(); ) {
548                 target.pasteElement((Element)it.next(), delete);
549             }
550             if (delete)
551                 return ExTransferable.EMPTY;
552             else
553                 return null;
554         }
555     }
556 }
557
Popular Tags