1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.manifest.model;
18 
19 import com.android.SdkConstants;
20 import com.android.ide.eclipse.adt.AdtConstants;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
23 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
25 import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
26 import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper;
27 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
28 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiTextAttributeNode;
29 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
30 import com.android.xml.AndroidManifest;
31 
32 import org.eclipse.core.resources.IFile;
33 import org.eclipse.core.resources.IProject;
34 import org.eclipse.core.runtime.CoreException;
35 import org.eclipse.core.runtime.NullProgressMonitor;
36 import org.eclipse.jdt.core.Flags;
37 import org.eclipse.jdt.core.IClasspathEntry;
38 import org.eclipse.jdt.core.IJavaElement;
39 import org.eclipse.jdt.core.IJavaProject;
40 import org.eclipse.jdt.core.IPackageFragment;
41 import org.eclipse.jdt.core.IPackageFragmentRoot;
42 import org.eclipse.jdt.core.IType;
43 import org.eclipse.jdt.core.ITypeHierarchy;
44 import org.eclipse.jdt.core.JavaCore;
45 import org.eclipse.jdt.core.JavaModelException;
46 import org.eclipse.jdt.core.search.IJavaSearchScope;
47 import org.eclipse.jdt.core.search.SearchEngine;
48 import org.eclipse.jdt.ui.IJavaElementSearchConstants;
49 import org.eclipse.jdt.ui.JavaUI;
50 import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
51 import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension;
52 import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor;
53 import org.eclipse.jdt.ui.dialogs.ITypeSelectionComponent;
54 import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension;
55 import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
56 import org.eclipse.jface.dialogs.IMessageProvider;
57 import org.eclipse.jface.window.Window;
58 import org.eclipse.swt.SWT;
59 import org.eclipse.swt.events.DisposeEvent;
60 import org.eclipse.swt.events.DisposeListener;
61 import org.eclipse.swt.events.ModifyEvent;
62 import org.eclipse.swt.events.ModifyListener;
63 import org.eclipse.swt.events.SelectionAdapter;
64 import org.eclipse.swt.events.SelectionEvent;
65 import org.eclipse.swt.layout.GridData;
66 import org.eclipse.swt.layout.GridLayout;
67 import org.eclipse.swt.widgets.Button;
68 import org.eclipse.swt.widgets.Composite;
69 import org.eclipse.swt.widgets.Control;
70 import org.eclipse.swt.widgets.Text;
71 import org.eclipse.ui.IEditorInput;
72 import org.eclipse.ui.IFileEditorInput;
73 import org.eclipse.ui.PartInitException;
74 import org.eclipse.ui.PlatformUI;
75 import org.eclipse.ui.dialogs.SelectionDialog;
76 import org.eclipse.ui.forms.IManagedForm;
77 import org.eclipse.ui.forms.events.HyperlinkAdapter;
78 import org.eclipse.ui.forms.events.HyperlinkEvent;
79 import org.eclipse.ui.forms.widgets.FormText;
80 import org.eclipse.ui.forms.widgets.FormToolkit;
81 import org.eclipse.ui.forms.widgets.TableWrapData;
82 import org.w3c.dom.Element;
83 
84 import java.util.ArrayList;
85 import java.util.Collections;
86 import java.util.HashSet;
87 import java.util.List;
88 import java.util.Set;
89 
90 /**
91  * Represents an XML attribute for a class, that can be modified using a simple text field or
92  * a dialog to choose an existing class. Also, there's a link to create a new class.
93  * <p/>
94  * See {@link UiTextAttributeNode} for more information.
95  */
96 public class UiClassAttributeNode extends UiTextAttributeNode {
97 
98     private String mReferenceClass;
99     private IPostTypeCreationAction mPostCreationAction;
100     private boolean mMandatory;
101     private final boolean mDefaultToProjectOnly;
102 
103     private class HierarchyTypeSelection extends TypeSelectionExtension {
104 
105         private IJavaProject mJavaProject;
106         private IType mReferenceType;
107         private Button mProjectOnly;
108         private boolean mUseProjectOnly;
109 
HierarchyTypeSelection(IProject project, String referenceClass)110         public HierarchyTypeSelection(IProject project, String referenceClass)
111                 throws JavaModelException {
112             mJavaProject = JavaCore.create(project);
113             mReferenceType = mJavaProject.findType(referenceClass);
114         }
115 
116         @Override
getFilterExtension()117         public ITypeInfoFilterExtension getFilterExtension() {
118             return new ITypeInfoFilterExtension() {
119                 @Override
120                 public boolean select(ITypeInfoRequestor typeInfoRequestor) {
121 
122                     boolean projectOnly = mUseProjectOnly;
123 
124                     String packageName = typeInfoRequestor.getPackageName();
125                     String typeName = typeInfoRequestor.getTypeName();
126                     String enclosingType = typeInfoRequestor.getEnclosingName();
127 
128                     // build the full class name.
129                     StringBuilder sb = new StringBuilder(packageName);
130                     sb.append('.');
131                     if (enclosingType.length() > 0) {
132                         sb.append(enclosingType);
133                         sb.append('.');
134                     }
135                     sb.append(typeName);
136 
137                     String className = sb.toString();
138 
139                     try {
140                         IType type = mJavaProject.findType(className);
141 
142                         if (type == null) {
143                             return false;
144                         }
145 
146                         // don't display abstract classes
147                         if ((type.getFlags() & Flags.AccAbstract) != 0) {
148                             return false;
149                         }
150 
151                         // if project-only is selected, make sure the package fragment is
152                         // an actual source (thus "from this project").
153                         if (projectOnly) {
154                             IPackageFragment frag = type.getPackageFragment();
155                             if (frag == null || frag.getKind() != IPackageFragmentRoot.K_SOURCE) {
156                                 return false;
157                             }
158                         }
159 
160                         // get the type hierarchy and reference type is one of the super classes.
161                         ITypeHierarchy hierarchy = type.newSupertypeHierarchy(
162                                 new NullProgressMonitor());
163 
164                         IType[] supertypes = hierarchy.getAllSupertypes(type);
165                         int n = supertypes.length;
166                         for (int i = 0; i < n; i++) {
167                             IType st = supertypes[i];
168                             if (mReferenceType.equals(st)) {
169                                 return true;
170                             }
171                         }
172                     } catch (JavaModelException e) {
173                     }
174 
175                     return false;
176                 }
177             };
178         }
179 
180         @Override
createContentArea(Composite parent)181         public Control createContentArea(Composite parent) {
182 
183             mProjectOnly = new Button(parent, SWT.CHECK);
184             mProjectOnly.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
185             mProjectOnly.setText(String.format("Display classes from sources of project '%s' only",
186                     mJavaProject.getProject().getName()));
187 
188             mUseProjectOnly = mDefaultToProjectOnly;
189             mProjectOnly.setSelection(mUseProjectOnly);
190 
191             mProjectOnly.addSelectionListener(new SelectionAdapter() {
192                 @Override
193                 public void widgetSelected(SelectionEvent e) {
194                     super.widgetSelected(e);
195                     mUseProjectOnly = mProjectOnly.getSelection();
196                     getTypeSelectionComponent().triggerSearch();
197                 }
198             });
199 
200             return super.createContentArea(parent);
201         }
202     }
203 
204     /**
205      * Classes which implement this interface provide a method processing newly created classes.
206      */
207     public static interface IPostTypeCreationAction {
208         /**
209          * Sent to process a newly created class.
210          * @param newType the IType representing the newly created class.
211          */
212         public void processNewType(IType newType);
213     }
214 
215     /**
216      * Creates a {@link UiClassAttributeNode} object that will display ui to select or create
217      * classes.
218      * @param referenceClass The allowed supertype of the classes that are to be selected
219      * or created. Can be null.
220      * @param postCreationAction a {@link IPostTypeCreationAction} object handling post creation
221      * modification of the class.
222      * @param mandatory indicates if the class value is mandatory
223      * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node.
224      * @param defaultToProjectOnly When true display classes of this project only by default.
225      *         When false any class path will be considered. The user can always toggle this.
226      */
227     public UiClassAttributeNode(String referenceClass, IPostTypeCreationAction postCreationAction,
228             boolean mandatory, AttributeDescriptor attributeDescriptor, UiElementNode uiParent,
229             boolean defaultToProjectOnly) {
230         super(attributeDescriptor, uiParent);
231 
232         mReferenceClass = referenceClass;
233         mPostCreationAction = postCreationAction;
234         mMandatory = mandatory;
235         mDefaultToProjectOnly = defaultToProjectOnly;
236     }
237 
238     /* (non-java doc)
239      * Creates a label widget and an associated text field.
240      * <p/>
241      * As most other parts of the android manifest editor, this assumes the
242      * parent uses a table layout with 2 columns.
243      */
244     @Override
245     public void createUiControl(final Composite parent, IManagedForm managedForm) {
246         setManagedForm(managedForm);
247         FormToolkit toolkit = managedForm.getToolkit();
248         TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor();
249 
250         StringBuilder label = new StringBuilder();
251         label.append("<form><p><a href='unused'>");
252         label.append(desc.getUiName());
253         label.append("</a></p></form>");
254         FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */,
255                 label.toString(), true /* setupLayoutData */);
256         formText.addHyperlinkListener(new HyperlinkAdapter() {
257             @Override
258             public void linkActivated(HyperlinkEvent e) {
259                 super.linkActivated(e);
260                 handleLabelClick();
261             }
262         });
263         formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE));
264         SectionHelper.addControlTooltip(formText, desc.getTooltip());
265 
266         Composite composite = toolkit.createComposite(parent);
267         composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE));
268         GridLayout gl = new GridLayout(2, false);
269         gl.marginHeight = gl.marginWidth = 0;
270         composite.setLayout(gl);
271         // Fixes missing text borders under GTK... also requires adding a 1-pixel margin
272         // for the text field below
273         toolkit.paintBordersFor(composite);
274 
275         final Text text = toolkit.createText(composite, getCurrentValue());
276         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
277         gd.horizontalIndent = 1;  // Needed by the fixed composite borders under GTK
278         text.setLayoutData(gd);
279         Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH);
280 
281         setTextWidget(text);
282 
283         browseButton.addSelectionListener(new SelectionAdapter() {
284             @Override
285             public void widgetSelected(SelectionEvent e) {
286                 super.widgetSelected(e);
287                 handleBrowseClick();
288             }
289         });
290     }
291 
292     /* (non-java doc)
293      *
294      * Add a modify listener that will check the validity of the class
295      */
296     @Override
297     protected void onAddValidators(final Text text) {
298         ModifyListener listener = new ModifyListener() {
299             @Override
300             public void modifyText(ModifyEvent e) {
301                 try {
302                     String textValue = text.getText().trim();
303                     if (textValue.length() == 0) {
304                         if (mMandatory) {
305                             setErrorMessage("Value is mandatory", text);
306                         } else {
307                             setErrorMessage(null, text);
308                         }
309                         return;
310                     }
311                     // first we need the current java package.
312                     String javaPackage = getManifestPackage();
313 
314                     // build the fully qualified name of the class
315                     String className = AndroidManifest.combinePackageAndClassName(
316                             javaPackage, textValue);
317 
318                     // only test the vilibility for activities.
319                     boolean testVisibility = SdkConstants.CLASS_ACTIVITY.equals(
320                             mReferenceClass);
321 
322                     // test the class
323                     setErrorMessage(BaseProjectHelper.testClassForManifest(
324                             BaseProjectHelper.getJavaProject(getProject()), className,
325                             mReferenceClass, testVisibility), text);
326                 } catch (CoreException ce) {
327                     setErrorMessage(ce.getMessage(), text);
328                 }
329             }
330         };
331 
332         text.addModifyListener(listener);
333 
334         // Make sure the validator removes its message(s) when the widget is disposed
335         text.addDisposeListener(new DisposeListener() {
336             @Override
337             public void widgetDisposed(DisposeEvent e) {
338                 // we don't want to use setErrorMessage, because we don't want to reset
339                 // the error flag in the UiAttributeNode
340                 getManagedForm().getMessageManager().removeMessage(text, text);
341             }
342         });
343 
344         // Finally call the validator once to make sure the initial value is processed
345         listener.modifyText(null);
346     }
347 
348     private void handleBrowseClick() {
349         Text text = getTextWidget();
350 
351         // we need to get the project of the manifest.
352         IProject project = getProject();
353         if (project != null) {
354 
355             // Create a search scope including only the source folder of the current
356             // project.
357             IPackageFragmentRoot[] packageFragmentRoots = getPackageFragmentRoots(project,
358                     true /*include_containers*/);
359             IJavaSearchScope scope = SearchEngine.createJavaSearchScope(
360                     packageFragmentRoots,
361                     false);
362 
363             try {
364                 SelectionDialog dlg = JavaUI.createTypeDialog(text.getShell(),
365                     PlatformUI.getWorkbench().getProgressService(),
366                     scope,
367                     IJavaElementSearchConstants.CONSIDER_CLASSES,  // style
368                     false, // no multiple selection
369                     "**",  //$NON-NLS-1$ //filter
370                     new HierarchyTypeSelection(project, mReferenceClass));
371                 dlg.setMessage(String.format("Select class name for element %1$s:",
372                         getUiParent().getBreadcrumbTrailDescription(false /* include_root */)));
373                 if (dlg instanceof ITypeSelectionComponent) {
374                     ((ITypeSelectionComponent)dlg).triggerSearch();
375                 }
376 
377                 if (dlg.open() == Window.OK) {
378                     Object[] results = dlg.getResult();
379                     if (results.length == 1) {
380                         handleNewType((IType)results[0]);
381                     }
382                 }
383             } catch (JavaModelException e1) {
384                 AdtPlugin.log(e1, "UiClassAttributeNode HandleBrowser failed");
385             }
386         }
387     }
388 
389     private void handleLabelClick() {
390         // get the current value
391         String className = getTextWidget().getText().trim();
392 
393         // get the package name from the manifest.
394         String packageName = getManifestPackage();
395 
396         if (className.length() == 0) {
397             createNewClass(packageName, null /* className */);
398         } else {
399             // build back the fully qualified class name.
400             String fullClassName = className;
401             if (className.startsWith(".")) { //$NON-NLS-1$
402                 fullClassName = packageName + className;
403             } else {
404                 String[] segments = className.split(AdtConstants.RE_DOT);
405                 if (segments.length == 1) {
406                     fullClassName = packageName + "." + className; //$NON-NLS-1$
407                 }
408             }
409 
410             // in case the type is enclosed, we need to replace the $ with .
411             fullClassName = fullClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$
412 
413             // now we try to find the file that contains this class and we open it in the editor.
414             IProject project = getProject();
415             IJavaProject javaProject = JavaCore.create(project);
416 
417             try {
418                 IType result = javaProject.findType(fullClassName);
419                 if (result != null) {
420                     JavaUI.openInEditor(result);
421                 } else {
422                     // split the last segment from the fullClassname
423                     int index = fullClassName.lastIndexOf('.');
424                     if (index != -1) {
425                         createNewClass(fullClassName.substring(0, index),
426                                 fullClassName.substring(index+1));
427                     } else {
428                         createNewClass(packageName, className);
429                     }
430                 }
431             } catch (JavaModelException e) {
432                 AdtPlugin.log(e, "UiClassAttributeNode HandleLabel failed");
433             } catch (PartInitException e) {
434                 AdtPlugin.log(e, "UiClassAttributeNode HandleLabel failed");
435             }
436         }
437     }
438 
439     private IProject getProject() {
440         UiElementNode uiNode = getUiParent();
441         AndroidXmlEditor editor = uiNode.getEditor();
442         IEditorInput input = editor.getEditorInput();
443         if (input instanceof IFileEditorInput) {
444             // from the file editor we can get the IFile object, and from it, the IProject.
445             IFile file = ((IFileEditorInput)input).getFile();
446             return file.getProject();
447         }
448 
449         return null;
450     }
451 
452 
453     /**
454      * Returns the current value of the /manifest/package attribute.
455      * @return the package or an empty string if not found
456      */
457     private String getManifestPackage() {
458         // get the root uiNode to get the 'package' attribute value.
459         UiElementNode rootNode = getUiParent().getUiRoot();
460 
461         Element xmlElement = (Element) rootNode.getXmlNode();
462 
463         if (xmlElement != null) {
464             return xmlElement.getAttribute(AndroidManifestDescriptors.PACKAGE_ATTR);
465         }
466         return ""; //$NON-NLS-1$
467     }
468 
469 
470     /**
471      * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of
472      * the specified project.
473      * @param project the project
474      * @param include_containers True to include containers
475      * @return an array of IPackageFragmentRoot.
476      */
477     private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project,
478             boolean include_containers) {
479         ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>();
480         try {
481             IJavaProject javaProject = JavaCore.create(project);
482             IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
483             for (int i = 0; i < roots.length; i++) {
484                 IClasspathEntry entry = roots[i].getRawClasspathEntry();
485                 if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE ||
486                         (include_containers &&
487                                 entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER)) {
488                     result.add(roots[i]);
489                 }
490             }
491         } catch (JavaModelException e) {
492         }
493 
494         return result.toArray(new IPackageFragmentRoot[result.size()]);
495     }
496 
497     private void handleNewType(IType type) {
498         Text text = getTextWidget();
499 
500         // get the fully qualified name with $ to properly detect the enclosing types.
501         String name = type.getFullyQualifiedName('$');
502 
503         String packageValue = getManifestPackage();
504 
505         // check if the class doesn't start with the package.
506         if (packageValue.length() > 0 && name.startsWith(packageValue)) {
507             // if it does, we remove the package and the first dot.
508             name = name.substring(packageValue.length() + 1);
509 
510             // look for how many segments we have left.
511             // if one, just write it that way.
512             // if more than one, write it with a leading dot.
513             String[] packages = name.split(AdtConstants.RE_DOT);
514             if (packages.length == 1) {
515                 text.setText(name);
516             } else {
517                 text.setText("." + name); //$NON-NLS-1$
518             }
519         } else {
520             text.setText(name);
521         }
522     }
523 
524     private void createNewClass(String packageName, String className) {
525         // create the wizard page for the class creation, and configure it
526         NewClassWizardPage page = new NewClassWizardPage();
527 
528         // set the parent class
529         page.setSuperClass(mReferenceClass, true /* canBeModified */);
530 
531         // get the source folders as java elements.
532         IPackageFragmentRoot[] roots = getPackageFragmentRoots(getProject(),
533                 true /*include_containers*/);
534 
535         IPackageFragmentRoot currentRoot = null;
536         IPackageFragment currentFragment = null;
537         int packageMatchCount = -1;
538 
539         for (IPackageFragmentRoot root : roots) {
540             // Get the java element for the package.
541             // This method is said to always return a IPackageFragment even if the
542             // underlying folder doesn't exist...
543             IPackageFragment fragment = root.getPackageFragment(packageName);
544             if (fragment != null && fragment.exists()) {
545                 // we have a perfect match! we use it.
546                 currentRoot = root;
547                 currentFragment = fragment;
548                 packageMatchCount = -1;
549                 break;
550             } else {
551                 // we don't have a match. we look for the fragment with the best match
552                 // (ie the closest parent package we can find)
553                 try {
554                     IJavaElement[] children;
555                     children = root.getChildren();
556                     for (IJavaElement child : children) {
557                         if (child instanceof IPackageFragment) {
558                             fragment = (IPackageFragment)child;
559                             if (packageName.startsWith(fragment.getElementName())) {
560                                 // its a match. get the number of segments
561                                 String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$
562                                 if (segments.length > packageMatchCount) {
563                                     packageMatchCount = segments.length;
564                                     currentFragment = fragment;
565                                     currentRoot = root;
566                                 }
567                             }
568                         }
569                     }
570                 } catch (JavaModelException e) {
571                     // Couldn't get the children: we just ignore this package root.
572                 }
573             }
574         }
575 
576         ArrayList<IPackageFragment> createdFragments = null;
577 
578         if (currentRoot != null) {
579             // if we have a perfect match, we set it and we're done.
580             if (packageMatchCount == -1) {
581                 page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
582                 page.setPackageFragment(currentFragment, true /* canBeModified */);
583             } else {
584                 // we have a partial match.
585                 // create the package. We have to start with the first segment so that we
586                 // know what to delete in case of a cancel.
587                 try {
588                     createdFragments = new ArrayList<IPackageFragment>();
589 
590                     int totalCount = packageName.split("\\.").length; //$NON-NLS-1$
591                     int count = 0;
592                     int index = -1;
593                     // skip the matching packages
594                     while (count < packageMatchCount) {
595                         index = packageName.indexOf('.', index+1);
596                         count++;
597                     }
598 
599                     // create the rest of the segments, except for the last one as indexOf will
600                     // return -1;
601                     while (count < totalCount - 1) {
602                         index = packageName.indexOf('.', index+1);
603                         count++;
604                         createdFragments.add(currentRoot.createPackageFragment(
605                                 packageName.substring(0, index),
606                                 true /* force*/, new NullProgressMonitor()));
607                     }
608 
609                     // create the last package
610                     createdFragments.add(currentRoot.createPackageFragment(
611                             packageName, true /* force*/, new NullProgressMonitor()));
612 
613                     // set the root and fragment in the Wizard page
614                     page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
615                     page.setPackageFragment(createdFragments.get(createdFragments.size()-1),
616                             true /* canBeModified */);
617                 } catch (JavaModelException e) {
618                     // if we can't create the packages, there's a problem. we revert to the default
619                     // package
620                     for (IPackageFragmentRoot root : roots) {
621                         // Get the java element for the package.
622                         // This method is said to always return a IPackageFragment even if the
623                         // underlying folder doesn't exist...
624                         IPackageFragment fragment = root.getPackageFragment(packageName);
625                         if (fragment != null && fragment.exists()) {
626                             page.setPackageFragmentRoot(root, true /* canBeModified*/);
627                             page.setPackageFragment(fragment, true /* canBeModified */);
628                             break;
629                         }
630                     }
631                 }
632             }
633         } else if (roots.length > 0) {
634             // if we haven't found a valid fragment, we set the root to the first source folder.
635             page.setPackageFragmentRoot(roots[0], true /* canBeModified*/);
636         }
637 
638         // if we have a starting class name we use it
639         if (className != null) {
640             page.setTypeName(className, true /* canBeModified*/);
641         }
642 
643         // create the action that will open it the wizard.
644         OpenNewClassWizardAction action = new OpenNewClassWizardAction();
645         action.setConfiguredWizardPage(page);
646         action.run();
647         IJavaElement element = action.getCreatedElement();
648 
649         if (element != null) {
650             if (element.getElementType() == IJavaElement.TYPE) {
651 
652                 IType type = (IType)element;
653 
654                 if (mPostCreationAction != null) {
655                     mPostCreationAction.processNewType(type);
656                 }
657 
658                 handleNewType(type);
659             }
660         } else {
661             // lets delete the packages we created just for this.
662             // we need to start with the leaf and go up
663             if (createdFragments != null) {
664                 try {
665                     for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) {
666                         createdFragments.get(i).delete(true /* force*/, new NullProgressMonitor());
667                     }
668                 } catch (JavaModelException e) {
669                     e.printStackTrace();
670                 }
671             }
672         }
673     }
674 
675     /**
676      * Sets the error messages. If message is <code>null</code>, the message is removed.
677      * @param message the message to set, or <code>null</code> to remove the current message
678      * @param textWidget the {@link Text} widget associated to the message.
679      */
680     private final void setErrorMessage(String message, Text textWidget) {
681         if (message != null) {
682             setHasError(true);
683             getManagedForm().getMessageManager().addMessage(textWidget, message, null /* data */,
684                     IMessageProvider.ERROR, textWidget);
685         } else {
686             setHasError(false);
687             getManagedForm().getMessageManager().removeMessage(textWidget, textWidget);
688         }
689     }
690 
691     @Override
692     public String[] getPossibleValues(String prefix) {
693         // Compute a list of existing classes for content assist completion
694         IProject project = getProject();
695         if (project == null || mReferenceClass == null) {
696             return null;
697         }
698 
699         try {
700             IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
701             IType type = javaProject.findType(mReferenceClass);
702             // Use sets because query sometimes repeats the same class
703             Set<String> libraryTypes = new HashSet<String>(80);
704             Set<String> localTypes = new HashSet<String>(30);
705             if (type != null) {
706                 ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
707                 IType[] allSubtypes = hierarchy.getAllSubtypes(type);
708                 for (IType subType : allSubtypes) {
709                     int flags = subType.getFlags();
710                     if (Flags.isPublic(flags) && !Flags.isAbstract(flags)) {
711                         String fqcn = subType.getFullyQualifiedName();
712                         if (subType.getResource() != null) {
713                             localTypes.add(fqcn);
714                         } else {
715                             libraryTypes.add(fqcn);
716                         }
717                     }
718                 }
719             }
720 
721             List<String> local = new ArrayList<String>(localTypes);
722             List<String> library = new ArrayList<String>(libraryTypes);
723             Collections.sort(local);
724             Collections.sort(library);
725             List<String> combined = new ArrayList<String>(local.size() + library.size());
726             combined.addAll(local);
727             combined.addAll(library);
728             return combined.toArray(new String[combined.size()]);
729         } catch (Exception e) {
730             AdtPlugin.log(e, null);
731         }
732 
733         return null;
734     }
735 }
736 
737