|
What this is
This file is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Other links
The source code
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.src.nodes;
import java.awt.Component;
import java.awt.datatransfer.Transferable;
import java.beans.*;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.*;
import org.netbeans.api.java.classpath.ClassPath;
import org.openide.*;
import org.openide.src.*;
import org.openide.nodes.*;
import org.openide.cookies.SourceCookie;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.util.*;
import org.openide.util.datatransfer.NewType;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.datatransfer.ExTransferable;
/** This class defines utilities for editing source using hierarchy API,
* e.g. creation new types for class elements, runAtomicAsUser support, ...
*
* @author Petr Hamernik
*/
class SourceEditSupport {
static final ResourceBundle bundle = NbBundle.getBundle(SourceEditSupport.class);
static final String[] MENU_NAMES = {
bundle.getString("MENU_CREATE_BLOCK"), bundle.getString("MENU_CREATE_VARIABLE"),
bundle.getString("MENU_CREATE_CONSTRUCTOR"), bundle.getString("MENU_CREATE_METHOD"),
bundle.getString("MENU_CREATE_CLASS"), bundle.getString("MENU_CREATE_INTERFACE")
};
/* Get the new types that can be created in this node.
* For example, a node representing a Java package will permit classes to be added.
* @return array of new type operations that are allowed
*/
public static NewType[] createNewTypes(ClassElement element) {
if (element.isClass()) {
// class new types
return new NewType[] {
new ElementNewType(element, (byte) 0),
new ElementNewType(element, (byte) 1),
new ElementNewType(element, (byte) 2),
new ElementNewType(element, (byte) 3),
new ElementNewType(element, (byte) 4),
new ElementNewType(element, (byte) 5)
};
}
else {
// interface new types
return new NewType[] {
new ElementNewType(element, (byte) 1),
new ElementNewType(element, (byte) 3),
new ElementNewType(element, (byte) 4),
new ElementNewType(element, (byte) 5)
};
}
}
/** New types for class element */
static class ElementNewType extends NewType {
/** Class element where to create new element */
ClassElement element;
/** Filled in if the target class is an interface to fool customizers and
* have the restrictions on them.
*/
ClassElement proxy;
/** The kind of element to create */
byte kind;
/** Creates new type
* @param element Where to create new element.
* @param kind The kind of the element to create
*/
public ElementNewType(ClassElement element, byte kind) {
this.element = element;
proxy = new ClassElement();
try {
proxy.setName(Identifier.create("Default")); // NOI18N
proxy.setClassOrInterface(element.isClassOrInterface());
} catch (SourceException ex) {
// it can NOT happen
}
this.kind = kind;
}
/** Get the name of the new type.
* @return localized name.
*/
public String getName() {
return MENU_NAMES[kind];
}
/** Help context */
public org.openide.util.HelpCtx getHelpCtx() {
return new org.openide.util.HelpCtx (SourceEditSupport.class.getName () + ".newElement" + kind); // NOI18N
}
/** Creates new element */
public void create () throws IOException {
final Identifier outerName = element.getName();
final boolean outerIsClass = element.isClass();
Element newElement = null;
try {
switch (kind) {
case 0:
{
// Adding initializer
InitializerElement e = new InitializerElement();
e.setStatic(true);
e.setBody("\n"); // NOI18N
newElement = e;
break;
}
case 1:
{
// Adding field
FieldElement e = new FieldElement();
e.setType(Type.INT);
e.setName(Identifier.create("newField")); // NOI18N
e.setModifiers(Modifier.PRIVATE + (outerIsClass ? 0 : Modifier.STATIC));
proxy.addField(e);
e = proxy.getFields()[0];
FieldCustomizer cust = new FieldCustomizer(e);
if (openCustomizer(cust, "TIT_NewField") && cust.isOK()) // NOI18N
newElement = e;
break;
}
case 2:
{
// Adding constructor
ConstructorElement e = new ConstructorElement();
e.setName(Identifier.create(((ClassElement)element).getName().getName()));
e.setModifiers(Modifier.PUBLIC);
e.setBody("\n"); // NOI18N
MethodCustomizer cust = new MethodCustomizer(e);
if (openCustomizer(cust, "TIT_NewConstructor") && cust.isOK()) // NOI18N
newElement = e;
break;
}
case 3:
{
// Adding method
MethodElement e = new MethodElement();
e.setReturn(Type.VOID);
e.setName(Identifier.create("newMethod")); // NOI18N
e.setModifiers(Modifier.PUBLIC);
e.setBody(outerIsClass ? "\n" : null); // NOI18N
proxy.addMethod(e);
e = proxy.getMethods()[0];
MethodCustomizer cust = new MethodCustomizer(e);
if (openCustomizer(cust, "TIT_NewMethod") && cust.isOK()) {// NOI18N
if ((e.getModifiers() & (Modifier.ABSTRACT | Modifier.NATIVE)) > 0) {
// abstract and native methods cannot have bodies.
e.setBody(null);
}
newElement = e;
}
break;
}
case 4:
{
// Adding inner class
ClassElement e = new ClassElement();
e.setName(Identifier.create(outerName.getFullName() + ".InnerClass", "InnerClass")); // NOI18N
e.setModifiers(Modifier.PUBLIC);
e.setClassOrInterface(true);
proxy.addClass(e);
e = proxy.getClasses()[0];
ClassCustomizer cust = new ClassCustomizer(e);
if (openCustomizer(cust, "TIT_NewClass") && cust.isOK()) // NOI18N
newElement = e;
break;
}
case 5:
{
// Adding inner interface
ClassElement e = new ClassElement();
e.setName(Identifier.create(outerName.getFullName() + ".InnerInterface", "InnerInterface")); // NOI18N
e.setModifiers(Modifier.PUBLIC);
e.setClassOrInterface(false);
proxy.addClass(e);
e = proxy.getClasses()[0];
ClassCustomizer cust = new ClassCustomizer(e);
if (openCustomizer(cust, "TIT_NewInterface") && cust.isOK()) // NOI18N
newElement = e;
break;
}
}
}
catch (SourceException exc) {
// shouldn't happen - memory implementation
// is not based on java source.
}
if (newElement == null)
return;
final Element addingElement = newElement;
SourceEditSupport.invokeAtomicAsUser(element, new SourceEditSupport.ExceptionalRunnable() {
public void run() throws SourceException {
switch (kind) {
case 0:
((ClassElement)element).addInitializer((InitializerElement)addingElement);
return;
case 1:
((ClassElement)element).addField((FieldElement)addingElement);
return;
case 2:
((ClassElement)element).addConstructor((ConstructorElement)addingElement);
return;
case 3:
((ClassElement)element).addMethod((MethodElement)addingElement);
return;
case 4:
case 5:
element.addClass((ClassElement)addingElement);
return;
}
}
});
}
}
/** Show dialog and allow user to modify new element.
* @param customizer The component to be displayed
* @param titleKey the key to resource bundle for the title of dialog
* @return true if user pressed OK button,
* otherwise false (for CANCEL)
*/
static boolean openCustomizer(Component customizer, String titleKey) {
NotifyDescriptor desriptor = new NotifyDescriptor(
customizer,
ElementNode.bundle.getString(titleKey),
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.PLAIN_MESSAGE,
null, null);
Object ret = DialogDisplayer.getDefault().notify(desriptor);
return (ret == NotifyDescriptor.OK_OPTION);
}
/** Invokes the runnable using SourceElement.runAtomicAsUser, if it can find
* a source element for the given hierarchy element. If the SourceElement can't
* be found, the runnable is executed without any protection.
* @exception IOException If SourceException occured inside the runnable.
*/
static void invokeAtomicAsUser(Element element, final ExceptionalRunnable exRun) throws IOException {
try {
runAsUser(element, exRun);
} catch (SourceException.IO e) {
ErrorManager.getDefault().annotate(e.getReason(),
ErrorManager.USER, null, null, null, null);
throw e.getReason();
}
catch (SourceException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N
e.printStackTrace();
IOException x = new IOException(e.getMessage());
// #9512 -- this exception is expected, so lower its priority to "user".
ErrorManager.getDefault().annotate(x,
ErrorManager.USER, null, null, e, null);
throw x;
}
}
static void runAsUser(Element ref, final ExceptionalRunnable exRun) throws SourceException {
final SourceException ex[] = { null };
SourceElement src = findSource(ref);
boolean retry = false;
do {
ex[0] = null;
if (src == null) {
exRun.run();
} else {
src.runAtomicAsUser(new Runnable() {
public void run() {
try {
exRun.run();
} catch (SourceException e) {
ex[0] = e;
}
}
});
}
if (ex[0] != null) {
if (retry)
break;
retry = true;
if (ex[0] instanceof SourceException.IO) {
IOException iex = ((SourceException.IO)ex[0]).getReason();
if (iex instanceof UserQuestionException) {
UserQuestionException uex = (UserQuestionException)iex;
NotifyDescriptor.Confirmation nc = new NotifyDescriptor.Confirmation(uex.getLocalizedMessage(),
bundle.getString("TIT_CannotWriteFile"), NotifyDescriptor.Confirmation.YES_NO_OPTION);
Object o = DialogDisplayer.getDefault().notify(nc);
if (o == NotifyDescriptor.YES_OPTION) {
try {
uex.confirmed();
// HACK!! See issue #23407
continue;
} catch (IOException x) {
ex[0] = new SourceException.IO(x);
ErrorManager.getDefault().annotate(ex[0], ErrorManager.USER, null, null, x, null);
}
} else {
iex = new IOException("Cannot write");
ErrorManager.getDefault().annotate(iex, ErrorManager.USER, null, bundle.getString("ERR_CannotWriteFile"), uex, null);
ex[0] = new SourceException.IO(iex);
ErrorManager.getDefault().annotate(ex[0], ErrorManager.USER, null, bundle.getString("ERR_CannotWriteFile"), iex, null);
}
}
}
// not a UserQuestionException, unconfirmed UQE, exception from UQE.confirmed.
break;
}
} while (ex[0] != null);
if (ex[0] != null)
throw ex[0];
}
/** This interface is used like runnable, but its method run
* could throw BadLocationException.
* @exception SourceException
*/
static interface ExceptionalRunnable {
public void run() throws SourceException;
}
static boolean isWriteable(Element element) {
SourceElement el = findSource(element);
DataObject d = el == null ? null : (DataObject)el.getCookie(DataObject.class);
if (d == null) {
return true;
}
return !d.getPrimaryFile().isReadOnly();
}
/** Find the source for the specifier element.
* @return instance of SourceElement or null, if the source can't be found.
*/
static SourceElement findSource(Element element) {
SourceElement source = null;
ClassElement clazz = null;
if (element instanceof ClassElement) {
clazz = (ClassElement) element;
}
else if (element instanceof MemberElement) {
clazz = ((MemberElement) element).getDeclaringClass();
}
else if (element instanceof InitializerElement) {
clazz = ((InitializerElement) element).getDeclaringClass();
}
else if (element instanceof SourceElement) {
return (SourceElement) element;
}
if (clazz != null) {
source = clazz.getSource();
}
return source;
}
static void createJavaFile(ClassElement clazz, FileObject target) throws SourceException, IOException {
DataObject targetObject;
String name = clazz.getName().getSourceName();
FileObject newFile;
ClassPath cp = ClassPath.getClassPath(target, ClassPath.SOURCE);
if (cp == null) {
throw new IOException("No known package source root for " + target); // XXX I18N
}
String packageName = cp.getResourceName(target, '.', true);
String newName;
if ("".equals(packageName)) // NOI18N
packageName = null;
newName = org.openide.filesystems.FileUtil.findFreeFileName(target, name, "java"); // NOI18N
newFile = target.createData(name, "java"); // NOI18N
SourceElement newSrc;
SourceCookie cookie;
try {
targetObject = DataObject.find(newFile);
} catch (org.openide.loaders.DataObjectNotFoundException e) {
throw (IOException)ErrorManager.getDefault().annotate(
new IOException(e.getMessage()),
ErrorManager.EXCEPTION, "Data object can't be created", // NOI18N
bundle.getString("EXC_CREATE_SOURCE_FILE"),
e, null
);
}
cookie = (SourceCookie)targetObject.getCookie(SourceCookie.class);
if (cookie == null) {
// perhaps java sources not installed ?
throw (SourceException)ErrorManager.getDefault().annotate(
new SourceException("Source element cannot be found"), // NOI18N
bundle.getString("EXC_CREATE_SOURCE_FILE")
);
}
if (packageName != null) // NOI18N
cookie.getSource().setPackage(Identifier.create(packageName));
cookie.getSource().addClass(clazz);
ClassElement targetC = cookie.getSource().getClass(Identifier.create(clazz.getName().getSourceName()));
int mods = targetC.getModifiers() & ~Modifier.STATIC;
if ((mods & (Modifier.PROTECTED | Modifier.PRIVATE)) > 0) {
mods = (mods & ~(Modifier.PROTECTED | Modifier.PRIVATE)) | Modifier.PUBLIC;
}
targetC.setModifiers(mods);
}
static void removeClass(ClassElement clazz) throws SourceException {
if (clazz.getDeclaringClass() != null) {
clazz.getDeclaringClass().removeClass(clazz);
} else {
SourceElement src = SourceEditSupport.findSource(clazz);
if (src == null) {
throw (SourceException)ErrorManager.getDefault().annotate(
new SourceException("Element has no source"), // NOI18N
bundle.getString("EXC_NO_SOURCE")
);
}
src.removeClass(clazz);
}
}
/* default */static class PackagePaste implements NodeTransfer.Paste {
private static PasteType[] EMPTY_TYPES = new PasteType[0];
/** True, if the paste should remove the original class element.
*/
private boolean deleteSelf;
/** Class element to paste.
*/
private ClassElement element;
PackagePaste(ClassElement cls, boolean deleteSelf) {
this.deleteSelf = deleteSelf;
this.element = cls;
}
public PasteType[] types(Node target) {
DataObject obj = (DataObject)target.getCookie(DataObject.class);
if (element == null || obj == null)
return EMPTY_TYPES;
FileObject fob = obj.getPrimaryFile();
if (!fob.isFolder()) {
return EMPTY_TYPES;
}
return new PasteType[] {
new Type(fob)
};
}
private class Type extends PasteType {
/** Target file folder
*/
private FileObject target;
Type(FileObject target) {
this.target = target;
}
public String getName() {
return bundle.getString("MENU_PASTE_AS_FILE");
}
public org.openide.util.HelpCtx getHelpCtx() {
return super.getHelpCtx();
}
public Transferable paste() throws IOException {
final ClassElement clazz = PackagePaste.this.element;
final boolean del = PackagePaste.this.deleteSelf;
try {
createJavaFile(clazz, target);
} catch (SourceException ex) {
IOException x = new IOException(ex.getMessage());
ErrorManager.getDefault().annotate(x, ex);
throw x;
}
if (del) {
final SourceException ex[] = { null };
SourceEditSupport.invokeAtomicAsUser(clazz, new SourceEditSupport.ExceptionalRunnable() {
public void run() throws SourceException {
try {
removeClass(clazz);
} catch (SourceException e) {
ex[0] = e;
}
}
});
if (ex[0] != null) {
IOException x = new IOException(ex[0].getMessage());
ErrorManager.getDefault().annotate(x, ex[0]);
throw x;
}
PackagePaste.this.element = null;
return ExTransferable.EMPTY;
} else {
return null;
}
}
}
}
static class ClassMultiPasteType extends PasteType {
ClassElementNode target;
Collection members;
boolean delete;
ClassMultiPasteType(ClassElementNode target, Collection members, boolean delete) {
this.target = target;
this.members = members;
this.delete = delete;
}
public Transferable paste() throws IOException {
for (Iterator it = members.iterator(); it.hasNext(); ) {
target.pasteElement((Element)it.next(), delete);
}
if (delete)
return ExTransferable.EMPTY;
else
return null;
}
}
}
|