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.form.palette;
043:
044: import com.sun.source.tree.ClassTree;
045: import com.sun.source.tree.Tree;
046: import com.sun.source.util.TreePath;
047: import java.lang.ref.WeakReference;
048: import java.util.jar.*;
049: import java.util.*;
050: import java.io.*;
051: import java.lang.ref.Reference;
052: import java.lang.reflect.Modifier;
053: import java.net.URL;
054: import java.text.MessageFormat;
055: import java.util.logging.Level;
056: import java.util.logging.Logger;
057: import javax.lang.model.element.Element;
058: import javax.lang.model.element.ElementKind;
059: import javax.lang.model.element.ExecutableElement;
060: import javax.lang.model.element.TypeElement;
061: import org.netbeans.api.java.source.CancellableTask;
062: import org.netbeans.api.java.source.CompilationController;
063: import org.netbeans.api.java.source.JavaSource;
064: import org.netbeans.api.java.source.JavaSource.Phase;
065: import org.netbeans.modules.classfile.ClassFile;
066: import org.netbeans.modules.classfile.Method;
067:
068: import org.openide.*;
069: import org.openide.nodes.Node;
070: import org.openide.filesystems.*;
071: import org.openide.loaders.DataObject;
072:
073: import org.netbeans.modules.form.project.*;
074: import org.openide.util.Exceptions;
075:
076: /**
077: * This class provides methods for installing new items to Palete.
078: *
079: * @author Tomas Pavek
080: */
081:
082: public final class BeanInstaller {
083:
084: private static Reference<AddToPaletteWizard> wizardRef;
085:
086: private BeanInstaller() {
087: }
088:
089: // --------
090:
091: /** Installs beans from given source type. Lets the user choose the source,
092: * the beans, and the target category in a wizard. */
093: public static void installBeans(
094: Class<? extends ClassSource.Entry> sourceType) {
095: AddToPaletteWizard wizard = getAddWizard();
096: if (wizard.show(sourceType))
097: createPaletteItems(wizard.getSelectedBeans(), wizard
098: .getSelectedCategory());
099: }
100:
101: /** Installs beans represented by given nodes (selected by the user). Lets
102: * the user choose the palette category. */
103: public static void installBeans(Node[] nodes) {
104: final List<ClassSource> beans = new LinkedList<ClassSource>();
105: final List<String> unableToInstall = new LinkedList<String>();
106: final List<String> noBeans = new LinkedList<String>();
107: for (int i = 0; i < nodes.length; i++) {
108: DataObject dobj = nodes[i].getCookie(DataObject.class);
109: if (dobj == null)
110: continue;
111:
112: final FileObject fo = dobj.getPrimaryFile();
113: JavaClassHandler handler = new JavaClassHandler() {
114: public void handle(String className, String problem) {
115: if (problem == null) {
116: ClassSource classSource = ClassPathUtils
117: .getProjectClassSource(fo, className);
118: if (classSource == null) {
119: // Issue 47947
120: unableToInstall.add(className);
121: } else {
122: beans.add(classSource);
123: }
124: } else {
125: noBeans.add(className);
126: noBeans.add(problem);
127: }
128: }
129: };
130: scanFileObject(fo.getParent(), fo, handler);
131: }
132:
133: if (unableToInstall.size() > 0) {
134: Iterator iter = unableToInstall.iterator();
135: StringBuffer sb = new StringBuffer();
136: while (iter.hasNext()) {
137: sb.append(iter.next() + ", "); // NOI18N
138: }
139: sb.delete(sb.length() - 2, sb.length());
140: String messageFormat = PaletteUtils
141: .getBundleString("MSG_cannotInstallBeans"); // NOI18N
142: String message = MessageFormat.format(messageFormat,
143: new Object[] { sb.toString() });
144: NotifyDescriptor nd = new NotifyDescriptor.Message(message);
145: DialogDisplayer.getDefault().notify(nd);
146: if (beans.size() == 0)
147: return;
148: }
149:
150: String message = null;
151: if (beans.size() == 0) {
152: message = PaletteUtils
153: .getBundleString("MSG_noBeansUnderNodes"); // NOI18N
154: }
155: if (noBeans.size() != 0) {
156: Iterator<String> iter = noBeans.iterator();
157: while (iter.hasNext()) {
158: String className = iter.next();
159: String format = iter.next();
160: String msg = MessageFormat.format(format, className);
161: if (message != null) {
162: message += '\n';
163: } else {
164: message = ""; // NOI18N
165: }
166: message += msg;
167: }
168: }
169: if (message != null) {
170: NotifyDescriptor nd = new NotifyDescriptor.Message(message);
171: DialogDisplayer.getDefault().notify(nd);
172: }
173: if (beans.size() == 0)
174: return;
175:
176: String category = CategorySelector.selectCategory();
177: if (category == null)
178: return; // canceled by user
179:
180: final FileObject categoryFolder = PaletteUtils
181: .getPaletteFolder().getFileObject(category);
182: try {
183: Repository.getDefault().getDefaultFileSystem()
184: .runAtomicAction(new FileSystem.AtomicAction() {
185: public void run() {
186: Iterator it = beans.iterator();
187: while (it.hasNext()) {
188: ClassSource classSource = (ClassSource) it
189: .next();
190: try {
191: PaletteItemDataObject
192: .createFile(categoryFolder,
193: classSource);
194: // TODO check the class if it can be loaded?
195: } catch (java.io.IOException ex) {
196: ErrorManager.getDefault().notify(
197: ErrorManager.INFORMATIONAL,
198: ex);
199: }
200: }
201: }
202: });
203: } catch (java.io.IOException ex) {
204: } // should not happen
205: }
206:
207: /** Finds available JavaBeans in given JAR files. Looks for beans
208: * specified in the JAR manifest only.
209: */
210: static List<BeanInstaller.ItemInfo> findJavaBeansInJar(
211: List<? extends ClassSource.Entry> entries) {
212: Map<String, ItemInfo> beans = null;
213:
214: for (ClassSource.Entry entry : entries) {
215: for (URL root : entry.getClasspath()) {
216: URL jarU = FileUtil.getArchiveFile(root);
217: if (jarU == null) {
218: continue;
219: }
220: // Handle e.g. nbinst protocol.
221: FileObject jarFO = URLMapper.findFileObject(jarU);
222: if (jarFO == null) {
223: continue;
224: }
225: File jarF = FileUtil.toFile(jarFO);
226: if (jarF == null) {
227: continue;
228: }
229: Manifest mf;
230: try {
231: JarFile jf = new JarFile(jarF);
232: try {
233: mf = jf.getManifest();
234: } finally {
235: jf.close();
236: }
237: } catch (IOException x) {
238: Exceptions.printStackTrace(x);
239: continue;
240: }
241: if (mf == null) {
242: continue;
243: }
244: for (Map.Entry<String, Attributes> section : mf
245: .getEntries().entrySet()) {
246: if (!section.getKey().endsWith(".class")) { // NOI18N
247: continue;
248: }
249: String value = section.getValue().getValue(
250: "Java-Bean"); // NOI18N
251: if (!"True".equalsIgnoreCase(value)) { // NOI18N
252: continue;
253: }
254: String classname = section.getKey().substring(0,
255: section.getKey().length() - 6) // cut off ".class"
256: .replace('\\', '/').replace('/', '.');
257: if (classname.startsWith(".")) { // NOI18N
258: classname = classname.substring(1);
259: }
260: ItemInfo ii = new ItemInfo();
261: ii.classname = classname;
262: ii.entry = entry;
263: if (beans == null) {
264: beans = new HashMap<String, ItemInfo>(100);
265: }
266: beans.put(ii.classname, ii);
267: }
268: }
269: }
270:
271: return beans != null ? new ArrayList<ItemInfo>(beans.values())
272: : null;
273: }
274:
275: /** Collects all classes under given roots that could be used as JavaBeans.
276: * This method is supposed to search in JAR files or folders containing
277: * built classes.
278: */
279: static List<ItemInfo> findJavaBeans(
280: List<? extends ClassSource.Entry> entries) {
281: Map<String, ItemInfo> beans = new HashMap<String, ItemInfo>(100);
282:
283: for (ClassSource.Entry entry : entries) {
284: for (URL root : entry.getClasspath()) {
285: FileObject foRoot = URLMapper.findFileObject(root);
286: if (foRoot != null) {
287: scanFolderForBeans(foRoot, beans, entry);
288: }
289: }
290: }
291:
292: return new ArrayList<ItemInfo>(beans.values());
293: }
294:
295: // --------
296: // private methods
297:
298: /** Installs given beans (described by ItemInfo in array). */
299: private static void createPaletteItems(final ItemInfo[] beans,
300: String category) {
301: if (beans.length == 0)
302: return;
303:
304: final FileObject categoryFolder = PaletteUtils
305: .getPaletteFolder().getFileObject(category);
306: if (categoryFolder == null)
307: return;
308:
309: try {
310: Repository.getDefault().getDefaultFileSystem()
311: .runAtomicAction(new FileSystem.AtomicAction() {
312: public void run() {
313: for (int i = 0; i < beans.length; i++)
314: try {
315: PaletteItemDataObject.createFile(
316: categoryFolder,
317: new ClassSource(
318: beans[i].classname,
319: beans[i].entry));
320: // TODO check the class if it can be loaded?
321: } catch (java.io.IOException ex) {
322: ErrorManager.getDefault().notify(
323: ErrorManager.INFORMATIONAL,
324: ex);
325: }
326: }
327: });
328: } catch (java.io.IOException ex) {
329: } // should not happen
330: }
331:
332: /** Recursive method scanning folders for classes (class files) that could
333: * be JavaBeans. */
334: private static void scanFolderForBeans(FileObject folder,
335: final Map<String, ItemInfo> beans,
336: final ClassSource.Entry root) {
337: JavaClassHandler handler = new JavaClassHandler() {
338: public void handle(String className, String problem) {
339: if (problem == null) {
340: ItemInfo ii = new ItemInfo();
341: ii.classname = className;
342: ii.entry = root;
343: beans.put(ii.classname, ii);
344: }
345: }
346: };
347:
348: FileObject[] files = folder.getChildren();
349: for (int i = 0; i < files.length; i++) {
350: FileObject fo = files[i];
351: if (fo.isFolder()) {
352: scanFolderForBeans(fo, beans, root);
353: } else
354: try {
355: if ("class".equals(fo.getExt()) // NOI18N
356: && (DataObject.find(fo) != null)) {
357: scanFileObject(folder, fo, handler);
358: }
359: } catch (org.openide.loaders.DataObjectNotFoundException ex) {
360: } // should not happen
361: }
362: }
363:
364: private static void scanFileObject(FileObject folder,
365: final FileObject fileObject, final JavaClassHandler handler) {
366: if ("class".equals(fileObject.getExt())) { // NOI18N
367: processClassFile(fileObject, handler);
368: } else if ("java".equals(fileObject.getExt())) { // NOI18N
369: processJavaFile(fileObject, handler);
370: }
371: }
372:
373: /**
374: * finds bean's FQN if there is any.
375: * @param file file to search a bean
376: * @return null or the fqn
377: */
378: public static String findJavaBeanName(FileObject file) {
379: final String[] fqn = new String[1];
380: scanFileObject(null, file, new JavaClassHandler() {
381: public void handle(String className, String problem) {
382: if (problem == null) {
383: fqn[0] = className;
384: }
385: }
386: });
387: return fqn[0];
388: }
389:
390: private static void processJavaFile(final FileObject javaFO,
391: final JavaClassHandler handler) {
392: try {
393: JavaSource js = JavaSource.forFileObject(javaFO);
394: js.runUserActionTask(
395: new CancellableTask<CompilationController>() {
396: public void cancel() {
397: }
398:
399: public void run(CompilationController ctrl)
400: throws Exception {
401: ctrl.toPhase(Phase.ELEMENTS_RESOLVED);
402: TypeElement clazz = findClass(ctrl, javaFO
403: .getName());
404: if (clazz != null) {
405: handler.handle(clazz.getQualifiedName()
406: .toString(),
407: isDeclaredAsJavaBean(clazz));
408: }
409: }
410: }, true);
411: } catch (IOException ex) {
412: Logger.getLogger(BeanInstaller.class.getClass().getName())
413: .log(Level.SEVERE, javaFO.toString(), ex);
414: }
415: }
416:
417: private static TypeElement findClass(CompilationController ctrl,
418: String className) {
419: for (Tree decl : ctrl.getCompilationUnit().getTypeDecls()) {
420: if (className.equals(((ClassTree) decl).getSimpleName()
421: .toString())) {
422: TreePath path = ctrl.getTrees().getPath(
423: ctrl.getCompilationUnit(), decl);
424: TypeElement clazz = (TypeElement) ctrl.getTrees()
425: .getElement(path);
426: return clazz;
427: }
428: }
429: return null;
430: }
431:
432: private static void processClassFile(FileObject classFO,
433: JavaClassHandler handler) {
434: try {
435: // XXX rewrite this to use javax.lang.model.element.* as soon as JavaSource introduce .class files support
436: InputStream is = null;
437: ClassFile clazz;
438: try {
439: is = classFO.getInputStream();
440: clazz = new ClassFile(is, false);
441: } finally {
442: if (is != null) {
443: is.close();
444: }
445: }
446: if (clazz != null) {
447: handler.handle(clazz.getName().getExternalName(),
448: isDeclaredAsJavaBean(clazz));
449: }
450: } catch (IOException ex) {
451: Logger.getLogger(BeanInstaller.class.getClass().getName())
452: .log(Level.SEVERE, classFO.toString(), ex);
453: }
454:
455: }
456:
457: public static String isDeclaredAsJavaBean(TypeElement clazz) {
458: if (ElementKind.CLASS != clazz.getKind()) {
459: return PaletteUtils.getBundleString("MSG_notAClass"); // NOI18N
460: }
461:
462: Set<javax.lang.model.element.Modifier> mods = clazz
463: .getModifiers();
464: if (mods.contains(javax.lang.model.element.Modifier.ABSTRACT)) {
465: return PaletteUtils.getBundleString("MSG_abstractClass"); // NOI18N
466: }
467:
468: if (!mods.contains(javax.lang.model.element.Modifier.PUBLIC)) {
469: return PaletteUtils.getBundleString("MSG_notPublic"); // NOI18N
470: }
471:
472: for (Element member : clazz.getEnclosedElements()) {
473: mods = member.getModifiers();
474: if (ElementKind.CONSTRUCTOR == member.getKind()
475: && mods
476: .contains(javax.lang.model.element.Modifier.PUBLIC)
477: && ((ExecutableElement) member).getParameters()
478: .isEmpty()) {
479: return null;
480: }
481: }
482:
483: return PaletteUtils.getBundleString("MSG_noPublicConstructor"); // NOI18N
484: }
485:
486: public static String isDeclaredAsJavaBean(ClassFile clazz) {
487: int access = clazz.getAccess();
488:
489: if (Modifier.isInterface(access) || clazz.isAnnotation()
490: || clazz.isEnum() || clazz.isSynthetic()) {
491: return PaletteUtils.getBundleString("MSG_notAClass"); // NOI18N
492: }
493:
494: if (Modifier.isAbstract(access)) {
495: return PaletteUtils.getBundleString("MSG_abstractClass"); // NOI18N
496: }
497:
498: if (!Modifier.isPublic(access)) {
499: return PaletteUtils.getBundleString("MSG_notPublic"); // NOI18N
500: }
501:
502: for (Object omethod : clazz.getMethods()) {
503: Method method = (Method) omethod;
504: if (method.isPublic() && method.getParameters().isEmpty()
505: && "<init>".equals(method.getName())) { // NOI18N
506: return null;
507: }
508: }
509: return PaletteUtils.getBundleString("MSG_noPublicConstructor"); // NOI18N
510: }
511:
512: private static AddToPaletteWizard getAddWizard() {
513: AddToPaletteWizard wizard = null;
514: if (wizardRef != null)
515: wizard = wizardRef.get();
516: if (wizard == null) {
517: wizard = new AddToPaletteWizard();
518: wizardRef = new WeakReference<AddToPaletteWizard>(wizard);
519: }
520: return wizard;
521: }
522:
523: // --------
524:
525: static class ItemInfo implements Comparable<ItemInfo> {
526: String classname;
527: ClassSource.Entry entry;
528:
529: public int compareTo(ItemInfo ii) {
530: int i;
531: i = classname.lastIndexOf('.');
532: String name1 = i >= 0 ? classname.substring(i + 1)
533: : classname;
534: i = ii.classname.lastIndexOf('.');
535: String name2 = i >= 0 ? ii.classname.substring(i + 1)
536: : ii.classname;
537: return name1.compareTo(name2);
538: }
539: }
540:
541: private interface JavaClassHandler {
542: public void handle(String className, String problem);
543: }
544:
545: }
|