OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / wizards / newproject / NewTestProjectCreationPage.java
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 /*
18  * References:
19  * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard
20  * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
21  */
22
23 package com.android.ide.eclipse.adt.internal.wizards.newproject;
24
25 import com.android.ide.eclipse.adt.AdtPlugin;
26 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
27 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
29 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
30 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo;
31 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.MainInfo;
32 import com.android.sdklib.IAndroidTarget;
33 import com.android.sdklib.SdkConstants;
34 import com.android.sdklib.xml.ManifestData;
35 import com.android.sdkuilib.internal.widgets.SdkTargetSelector;
36
37 import org.eclipse.core.filesystem.URIUtil;
38 import org.eclipse.core.resources.IProject;
39 import org.eclipse.core.resources.IResource;
40 import org.eclipse.core.resources.IWorkspace;
41 import org.eclipse.core.resources.ResourcesPlugin;
42 import org.eclipse.core.runtime.IPath;
43 import org.eclipse.core.runtime.IStatus;
44 import org.eclipse.core.runtime.Path;
45 import org.eclipse.core.runtime.Platform;
46 import org.eclipse.jdt.core.IJavaProject;
47 import org.eclipse.jdt.core.JavaConventions;
48 import org.eclipse.jface.wizard.WizardPage;
49 import org.eclipse.osgi.util.TextProcessor;
50 import org.eclipse.swt.SWT;
51 import org.eclipse.swt.custom.ScrolledComposite;
52 import org.eclipse.swt.events.ControlAdapter;
53 import org.eclipse.swt.events.ControlEvent;
54 import org.eclipse.swt.events.ModifyEvent;
55 import org.eclipse.swt.events.ModifyListener;
56 import org.eclipse.swt.events.SelectionAdapter;
57 import org.eclipse.swt.events.SelectionEvent;
58 import org.eclipse.swt.graphics.Rectangle;
59 import org.eclipse.swt.layout.GridData;
60 import org.eclipse.swt.layout.GridLayout;
61 import org.eclipse.swt.widgets.Button;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Control;
64 import org.eclipse.swt.widgets.DirectoryDialog;
65 import org.eclipse.swt.widgets.Event;
66 import org.eclipse.swt.widgets.Group;
67 import org.eclipse.swt.widgets.Label;
68 import org.eclipse.swt.widgets.Listener;
69 import org.eclipse.swt.widgets.Text;
70
71 import java.io.File;
72 import java.net.URI;
73 import java.util.ArrayList;
74 import java.util.regex.Pattern;
75
76 /**
77  * NewAndroidProjectCreationPage is a project creation page that provides the
78  * following fields:
79  * <ul>
80  * <li> Project name
81  * <li> SDK Target
82  * <li> Application name
83  * <li> Package name
84  * <li> Activity name
85  * </ul>
86  * Note: this class is public so that it can be accessed from unit tests.
87  * It is however an internal class. Its API may change without notice.
88  * It should semantically be considered as a private final class.
89  * Do not derive from this class.
90  */
91 public class NewTestProjectCreationPage extends WizardPage {
92
93     // constants
94     static final String TEST_PAGE_NAME = "newAndroidTestProjectPage"; //$NON-NLS-1$
95
96     /** Initial value for all name fields (project, activity, application, package). Used
97      * whenever a value is requested before controls are created. */
98     private static final String INITIAL_NAME = "";  //$NON-NLS-1$
99     /** Initial value for the Use Default Location check box. */
100     private static final boolean INITIAL_USE_DEFAULT_LOCATION = true;
101     /** Initial value for the Create Test Project check box. */
102     private static final boolean INITIAL_CREATE_TEST_PROJECT = false;
103
104
105     /** Pattern for characters accepted in a project name. Since this will be used as a
106      * directory name, we're being a bit conservative on purpose. It cannot start with a space. */
107     private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$");  //$NON-NLS-1$
108     /** Last user-browsed location, static so that it be remembered for the whole session */
109     private static String sCustomLocationOsPath = "";  //$NON-NLS-1$
110
111     private final int MSG_NONE = 0;
112     private final int MSG_WARNING = 1;
113     private final int MSG_ERROR = 2;
114
115     /** Structure with the externally visible information from this Test Project page. */
116     private final TestInfo mInfo = new TestInfo();
117     /** Structure with the externally visible information from the Main Project page.
118      *  This is null if there's no such page, meaning the test project page is standalone. */
119     private IMainInfo mMainInfo;
120
121     // widgets
122     private Text mProjectNameField;
123     private Text mPackageNameField;
124     private Text mApplicationNameField;
125     private Button mUseDefaultLocation;
126     private Label mLocationLabel;
127     private Text mLocationPathField;
128     private Button mBrowseButton;
129     private Text mMinSdkVersionField;
130     private SdkTargetSelector mSdkTargetSelector;
131     private ITargetChangeListener mSdkTargetChangeListener;
132     private Button mCreateTestProjectField;
133     private Text mTestedProjectNameField;
134     private Button mProjectBrowseButton;
135     private ProjectChooserHelper mProjectChooserHelper;
136     private Button mTestSelfProjectRadio;
137     private Button mTestExistingProjectRadio;
138
139     /** A list of composites that are disabled when the "Create Test Project" toggle is off. */
140     private ArrayList<Composite> mToggleComposites = new ArrayList<Composite>();
141
142     private boolean mInternalProjectNameUpdate;
143     private boolean mInternalLocationPathUpdate;
144     private boolean mInternalPackageNameUpdate;
145     private boolean mInternalApplicationNameUpdate;
146     private boolean mInternalMinSdkVersionUpdate;
147     private boolean mInternalSdkTargetUpdate;
148     private IProject mExistingTestedProject;
149     private boolean mProjectNameModifiedByUser;
150     private boolean mApplicationNameModifiedByUser;
151     private boolean mPackageNameModifiedByUser;
152     private boolean mMinSdkVersionModifiedByUser;
153     private boolean mSdkTargetModifiedByUser;
154
155     private Label mTestTargetPackageLabel;
156
157     private String mLastExistingPackageName;
158
159
160     /**
161      * Creates a new project creation wizard page.
162      */
163     public NewTestProjectCreationPage() {
164         super(TEST_PAGE_NAME);
165         setPageComplete(false);
166         setTitle("New Android Test Project");
167         setDescription("Creates a new Android Test Project resource.");
168     }
169
170     // --- Getters used by NewProjectWizard ---
171
172     /**
173      * Structure that collects all externally visible information from this page.
174      * This is used by the calling wizard to actually do the work or by other pages.
175      */
176     public class TestInfo {
177
178         /** Returns true if a new Test Project should be created. */
179         public boolean getCreateTestProject() {
180             return mCreateTestProjectField == null ? true : mCreateTestProjectField.getSelection();
181         }
182
183         /**
184          * Returns the current project location path as entered by the user, or its
185          * anticipated initial value. Note that if the default has been returned the
186          * path in a project description used to create a project should not be set.
187          *
188          * @return the project location path or its anticipated initial value.
189          */
190         public IPath getLocationPath() {
191             return new Path(getProjectLocation());
192         }
193
194         /** Returns the value of the project name field with leading and trailing spaces removed. */
195         public String getProjectName() {
196             return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim();
197         }
198
199         /** Returns the value of the package name field with spaces trimmed. */
200         public String getPackageName() {
201             return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim();
202         }
203
204         /** Returns the value of the test target package name field with spaces trimmed. */
205         public String getTargetPackageName() {
206             return mTestTargetPackageLabel == null ? INITIAL_NAME
207                                                    : mTestTargetPackageLabel.getText().trim();
208         }
209
210         /** Returns the value of the min sdk version field with spaces trimmed. */
211         public String getMinSdkVersion() {
212             return mMinSdkVersionField == null ? "" : mMinSdkVersionField.getText().trim();  //$NON-NLS-1$
213         }
214
215         /** Returns the value of the application name field with spaces trimmed. */
216         public String getApplicationName() {
217             // Return the name of the activity as default application name.
218             return mApplicationNameField == null ? "" : mApplicationNameField.getText().trim();  //$NON-NLS-1$
219         }
220
221         /** Returns the value of the Use Default Location field. */
222         public boolean useDefaultLocation() {
223             return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION
224                                                : mUseDefaultLocation.getSelection();
225         }
226
227         /** Returns the the default "src" constant. */
228         public String getSourceFolder() {
229             return SdkConstants.FD_SOURCES;
230         }
231
232         /** Returns the current sdk target or null if none has been selected yet. */
233         public IAndroidTarget getSdkTarget() {
234             return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected();
235         }
236
237         public boolean isTestingSelf() {
238             return mMainInfo == null &&
239                 (mTestSelfProjectRadio == null ? false : mTestSelfProjectRadio.getSelection());
240         }
241
242         public boolean isTestingMain() {
243             return mMainInfo != null;
244         }
245
246         public boolean isTestingExisting() {
247             return mMainInfo == null &&
248                 (mTestExistingProjectRadio == null ? false
249                                                    : mTestExistingProjectRadio.getSelection());
250         }
251
252         public IProject getExistingTestedProject() {
253             return mExistingTestedProject;
254         }
255     }
256
257     /**
258      * Returns a {@link TestInfo} structure that collects all externally visible information
259      * from this page. This is used by the calling wizard to actually do the work or by other pages.
260      */
261     public TestInfo getTestInfo() {
262         return mInfo;
263     }
264
265     /**
266      * Grabs the {@link MainInfo} structure with visible parameters from the main project page.
267      * This may be null.
268      */
269     public void setMainInfo(IMainInfo mainInfo) {
270         mMainInfo = mainInfo;
271     }
272
273     // --- UI creation ---
274
275     /**
276      * Creates the top level control for this dialog page under the given parent
277      * composite.
278      *
279      * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
280      */
281     public void createControl(Composite parent) {
282         final ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
283         scrolledComposite.setFont(parent.getFont());
284         scrolledComposite.setExpandHorizontal(true);
285         scrolledComposite.setExpandVertical(true);
286         initializeDialogUnits(parent);
287
288         final Composite composite = new Composite(scrolledComposite, SWT.NULL);
289         composite.setFont(parent.getFont());
290         scrolledComposite.setContent(composite);
291
292         composite.setLayout(new GridLayout());
293         composite.setLayoutData(new GridData(GridData.FILL_BOTH));
294
295         createToggleTestProject(composite);
296         createTestProjectGroup(composite);
297         createLocationGroup(composite);
298         createTestTargetGroup(composite);
299         createTargetGroup(composite);
300         createPropertiesGroup(composite);
301
302         // Update state the first time
303         enableLocationWidgets();
304
305         scrolledComposite.addControlListener(new ControlAdapter() {
306             @Override
307             public void controlResized(ControlEvent e) {
308                 Rectangle r = scrolledComposite.getClientArea();
309                 scrolledComposite.setMinSize(composite.computeSize(r.width, SWT.DEFAULT));
310             }
311         });
312
313         // Show description the first time
314         setErrorMessage(null);
315         setMessage(null);
316         setControl(scrolledComposite);
317
318         // Validate. This will complain about the first empty field.
319         validatePageComplete();
320     }
321
322     /**
323      * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
324      * the dialog is made visible and to also update the enabled/disabled state of some
325      * controls (doing so in createControl doesn't always change their state somehow.)
326      */
327     @Override
328     public void setVisible(boolean visible) {
329         super.setVisible(visible);
330         if (visible) {
331             mProjectNameField.setFocus();
332             validatePageComplete();
333             onCreateTestProjectToggle();
334             onExistingProjectChanged();
335         }
336     }
337
338     @Override
339     public void dispose() {
340
341         if (mSdkTargetChangeListener != null) {
342             AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
343             mSdkTargetChangeListener = null;
344         }
345
346         super.dispose();
347     }
348
349     /**
350      * Creates the "create test project" checkbox but only if there's a main page in the wizard.
351      *
352      * @param parent the parent composite
353      */
354     private final void createToggleTestProject(Composite parent) {
355
356         if (mMainInfo != null) {
357             mCreateTestProjectField = new Button(parent, SWT.CHECK);
358             mCreateTestProjectField.setText("Create a Test Project");
359             mCreateTestProjectField.setToolTipText("Select this if you also want to create a Test Project.");
360             mCreateTestProjectField.setSelection(INITIAL_CREATE_TEST_PROJECT);
361             mCreateTestProjectField.addSelectionListener(new SelectionAdapter() {
362                 @Override
363                 public void widgetSelected(SelectionEvent e) {
364                     onCreateTestProjectToggle();
365                 }
366             });
367         }
368     }
369
370     /**
371      * Creates the group for the project name:
372      * [label: "Project Name"] [text field]
373      *
374      * @param parent the parent composite
375      */
376     private final void createTestProjectGroup(Composite parent) {
377         Composite group = new Composite(parent, SWT.NONE);
378         GridLayout layout = new GridLayout();
379         layout.numColumns = 2;
380         group.setLayout(layout);
381         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
382
383         mToggleComposites.add(group);
384
385         // --- test project name ---
386
387         // new project label
388         String tooltip = "Name of the Eclipse test project to create. It cannot be empty.";
389         Label label = new Label(group, SWT.NONE);
390         label.setText("Test Project Name:");
391         label.setFont(parent.getFont());
392         label.setToolTipText(tooltip);
393
394         // new project name entry field
395         mProjectNameField = new Text(group, SWT.BORDER);
396         GridData data = new GridData(GridData.FILL_HORIZONTAL);
397         mProjectNameField.setToolTipText(tooltip);
398         mProjectNameField.setLayoutData(data);
399         mProjectNameField.setFont(parent.getFont());
400         mProjectNameField.addListener(SWT.Modify, new Listener() {
401             public void handleEvent(Event event) {
402                 if (!mInternalProjectNameUpdate) {
403                     mProjectNameModifiedByUser = true;
404                 }
405                 updateLocationPathField(null);
406             }
407         });
408     }
409
410     private final void createLocationGroup(Composite parent) {
411
412         // --- project location ---
413
414         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
415         group.setLayout(new GridLayout(3, /* num columns */
416                 false /* columns of not equal size */));
417         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
418         group.setFont(parent.getFont());
419         group.setText("Content");
420
421         mToggleComposites.add(group);
422
423         mUseDefaultLocation = new Button(group, SWT.CHECK);
424         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
425         gd.horizontalSpan = 3;
426         mUseDefaultLocation.setLayoutData(gd);
427         mUseDefaultLocation.setText("Use default location");
428         mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION);
429
430         mUseDefaultLocation.addSelectionListener(new SelectionAdapter() {
431             @Override
432             public void widgetSelected(SelectionEvent e) {
433                 super.widgetSelected(e);
434                 enableLocationWidgets();
435                 validatePageComplete();
436             }
437         });
438
439
440         mLocationLabel = new Label(group, SWT.NONE);
441         mLocationLabel.setText("Location:");
442
443         mLocationPathField = new Text(group, SWT.BORDER);
444         GridData data = new GridData(GridData.FILL, /* horizontal alignment */
445                 GridData.BEGINNING, /* vertical alignment */
446                 true,  /* grabExcessHorizontalSpace */
447                 false, /* grabExcessVerticalSpace */
448                 1,     /* horizontalSpan */
449                 1);    /* verticalSpan */
450         mLocationPathField.setLayoutData(data);
451         mLocationPathField.setFont(parent.getFont());
452         mLocationPathField.addListener(SWT.Modify, new Listener() {
453            public void handleEvent(Event event) {
454                onLocationPathFieldModified();
455             }
456         });
457
458         mBrowseButton = new Button(group, SWT.PUSH);
459         mBrowseButton.setText("Browse...");
460         setButtonLayoutData(mBrowseButton);
461         mBrowseButton.addSelectionListener(new SelectionAdapter() {
462             @Override
463             public void widgetSelected(SelectionEvent e) {
464                 onOpenDirectoryBrowser();
465             }
466         });
467     }
468
469     /**
470      * Creates the group for Test Target options.
471      *
472      * There are two different modes here:
473      * <ul>
474      * <li>When mMainInfo exists, this is part of a new Android Project. In which case
475      * the new test is tied to the soon-to-be main project and there is actually no choice.
476      * <li>When mMainInfo does not exist, this is a standalone new test project. In this case
477      * we offer 2 options for the test target: self test or against an existing Android project.
478      * </ul>
479      *
480      * @param parent the parent composite
481      */
482     private final void createTestTargetGroup(Composite parent) {
483
484         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
485         GridLayout layout = new GridLayout();
486         layout.numColumns = 3;
487         group.setLayout(layout);
488         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
489         group.setFont(parent.getFont());
490         group.setText("Test Target");
491
492         mToggleComposites.add(group);
493
494         if (mMainInfo == null) {
495             // Standalone mode: choose between self-test and existing-project test
496
497             Label label = new Label(group, SWT.NONE);
498             label.setText("Select the project to test:");
499             GridData gd = new GridData(GridData.FILL_HORIZONTAL);
500             gd.horizontalSpan = 3;
501             label.setLayoutData(gd);
502
503             mTestSelfProjectRadio = new Button(group, SWT.RADIO);
504             mTestSelfProjectRadio.setText("This project");
505             mTestSelfProjectRadio.setSelection(false);
506             gd = new GridData(GridData.FILL_HORIZONTAL);
507             gd.horizontalSpan = 3;
508             mTestSelfProjectRadio.setLayoutData(gd);
509
510             mTestExistingProjectRadio = new Button(group, SWT.RADIO);
511             mTestExistingProjectRadio.setText("An existing Android project");
512             mTestExistingProjectRadio.setSelection(mMainInfo == null);
513             mTestExistingProjectRadio.addSelectionListener(new SelectionAdapter() {
514                 @Override
515                 public void widgetSelected(SelectionEvent e) {
516                     onExistingProjectChanged();
517                 }
518             });
519
520             String tooltip = "The existing Android Project that is being tested.";
521
522             mTestedProjectNameField = new Text(group, SWT.BORDER);
523             mTestedProjectNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
524             mTestedProjectNameField.setToolTipText(tooltip);
525             mTestedProjectNameField.addModifyListener(new ModifyListener() {
526                 public void modifyText(ModifyEvent e) {
527                     onProjectFieldUpdated();
528                 }
529             });
530
531             mProjectBrowseButton = new Button(group, SWT.NONE);
532             mProjectBrowseButton.setText("Browse...");
533             mProjectBrowseButton.setToolTipText("Allows you to select the Android project to test.");
534             mProjectBrowseButton.addSelectionListener(new SelectionAdapter() {
535                @Override
536                 public void widgetSelected(SelectionEvent e) {
537                    onProjectBrowse();
538                 }
539             });
540
541             mProjectChooserHelper = new ProjectChooserHelper(parent.getShell(), null /*filter*/);
542         } else {
543             // Part of NPW mode: no selection.
544
545         }
546
547         // package label line
548
549         Label label = new Label(group, SWT.NONE);
550         label.setText("Test Target Package:");
551         mTestTargetPackageLabel = new Label(group, SWT.NONE);
552         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
553         gd.horizontalSpan = 2;
554         mTestTargetPackageLabel.setLayoutData(gd);
555     }
556
557     /**
558      * Creates the target group.
559      * It only contains an SdkTargetSelector.
560      */
561     private void createTargetGroup(Composite parent) {
562         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
563         // Layout has 1 column
564         group.setLayout(new GridLayout());
565         group.setLayoutData(new GridData(GridData.FILL_BOTH));
566         group.setFont(parent.getFont());
567         group.setText("Build Target");
568
569         mToggleComposites.add(group);
570
571         // The selector is created without targets. They are added below in the change listener.
572         mSdkTargetSelector = new SdkTargetSelector(group, null);
573
574         mSdkTargetChangeListener = new ITargetChangeListener() {
575             public void onSdkLoaded() {
576                 // Update the sdk target selector with the new targets
577
578                 // get the targets from the sdk
579                 IAndroidTarget[] targets = null;
580                 if (Sdk.getCurrent() != null) {
581                     targets = Sdk.getCurrent().getTargets();
582                 }
583                 mSdkTargetSelector.setTargets(targets);
584
585                 // If there's only one target, select it
586                 if (targets != null && targets.length == 1) {
587                     mSdkTargetSelector.setSelection(targets[0]);
588                 }
589             }
590
591             public void onProjectTargetChange(IProject changedProject) {
592                 // Ignore
593             }
594
595             public void onTargetLoaded(IAndroidTarget target) {
596                 // Ignore
597             }
598         };
599
600         AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
601
602         // Invoke it once to initialize the targets
603         mSdkTargetChangeListener.onSdkLoaded();
604
605         mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
606             @Override
607             public void widgetSelected(SelectionEvent e) {
608                 onSdkTargetModified();
609                 updateLocationPathField(null);
610                 validatePageComplete();
611             }
612         });
613     }
614
615     /**
616      * Creates the group for the project properties:
617      * - Package name [text field]
618      * - Activity name [text field]
619      * - Application name [text field]
620      *
621      * @param parent the parent composite
622      */
623     private final void createPropertiesGroup(Composite parent) {
624         // package specification group
625         Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
626         GridLayout layout = new GridLayout();
627         layout.numColumns = 2;
628         group.setLayout(layout);
629         group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
630         group.setFont(parent.getFont());
631         group.setText("Properties");
632
633         mToggleComposites.add(group);
634
635         // new application label
636         Label label = new Label(group, SWT.NONE);
637         label.setText("Application name:");
638         label.setFont(parent.getFont());
639         label.setToolTipText("Name of the Application. This is a free string. It can be empty.");
640
641         // new application name entry field
642         mApplicationNameField = new Text(group, SWT.BORDER);
643         GridData data = new GridData(GridData.FILL_HORIZONTAL);
644         mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty.");
645         mApplicationNameField.setLayoutData(data);
646         mApplicationNameField.setFont(parent.getFont());
647         mApplicationNameField.addListener(SWT.Modify, new Listener() {
648            public void handleEvent(Event event) {
649                if (!mInternalApplicationNameUpdate) {
650                    mApplicationNameModifiedByUser = true;
651                }
652            }
653         });
654
655         // new package label
656         label = new Label(group, SWT.NONE);
657         label.setText("Package name:");
658         label.setFont(parent.getFont());
659         label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
660
661         // new package name entry field
662         mPackageNameField = new Text(group, SWT.BORDER);
663         data = new GridData(GridData.FILL_HORIZONTAL);
664         mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components.");
665         mPackageNameField.setLayoutData(data);
666         mPackageNameField.setFont(parent.getFont());
667         mPackageNameField.addListener(SWT.Modify, new Listener() {
668             public void handleEvent(Event event) {
669                 if (!mInternalPackageNameUpdate) {
670                     mPackageNameModifiedByUser = true;
671                 }
672                 onPackageNameFieldModified();
673             }
674         });
675
676         // min sdk version label
677         label = new Label(group, SWT.NONE);
678         label.setText("Min SDK Version:");
679         label.setFont(parent.getFont());
680         label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
681
682         // min sdk version entry field
683         mMinSdkVersionField = new Text(group, SWT.BORDER);
684         data = new GridData(GridData.FILL_HORIZONTAL);
685         label.setToolTipText("The minimum SDK version number that the application requires. Must be an integer > 0. It can be empty.");
686         mMinSdkVersionField.setLayoutData(data);
687         mMinSdkVersionField.setFont(parent.getFont());
688         mMinSdkVersionField.addListener(SWT.Modify, new Listener() {
689             public void handleEvent(Event event) {
690                 onMinSdkVersionFieldModified();
691                 validatePageComplete();
692             }
693         });
694     }
695
696
697     //--- Internal getters & setters ------------------
698
699     /** Returns the location path field value with spaces trimmed. */
700     private String getLocationPathFieldValue() {
701         return mLocationPathField == null ? "" : mLocationPathField.getText().trim();  //$NON-NLS-1$
702     }
703
704     /** Returns the current project location, depending on the Use Default Location check box. */
705     private String getProjectLocation() {
706         if (mInfo.useDefaultLocation()) {
707             return Platform.getLocation().toString();
708         } else {
709             return getLocationPathFieldValue();
710         }
711     }
712
713     /**
714      * Creates a project resource handle for the current project name field
715      * value.
716      * <p>
717      * This method does not create the project resource; this is the
718      * responsibility of <code>IProject::create</code> invoked by the new
719      * project resource wizard.
720      * </p>
721      *
722      * @return the new project resource handle
723      */
724     private IProject getProjectHandle() {
725         return ResourcesPlugin.getWorkspace().getRoot().getProject(mInfo.getProjectName());
726     }
727
728     // --- UI Callbacks ----
729
730     /**
731      * Callback invoked when the user toggles the "Test target: Existing Android Project"
732      * checkbox. It enables or disable the UI to select an existing project.
733      */
734     private void onExistingProjectChanged() {
735         if (mInfo.isTestingExisting()) {
736             boolean enabled = mTestExistingProjectRadio.getSelection();
737             mTestedProjectNameField.setEnabled(enabled);
738             mProjectBrowseButton.setEnabled(enabled);
739             setExistingProject(mInfo.getExistingTestedProject());
740             validatePageComplete();
741         }
742     }
743
744     /**
745      * Tries to load the defaults from the main page if possible.
746      */
747     private void useMainProjectInformation() {
748         if (mInfo.isTestingMain() && mMainInfo != null) {
749
750             String projName = String.format("%1$sTest", mMainInfo.getProjectName());
751             String appName = String.format("%1$sTest", mMainInfo.getApplicationName());
752
753             String packageName = mMainInfo.getPackageName();
754             if (packageName == null) {
755                 packageName = "";  //$NON-NLS-1$
756             }
757
758             updateTestTargetPackageField(packageName);
759
760             if (!mProjectNameModifiedByUser) {
761                 mInternalProjectNameUpdate = true;
762                 mProjectNameField.setText(projName);  //$NON-NLS-1$
763                 mInternalProjectNameUpdate = false;
764             }
765
766             if (!mApplicationNameModifiedByUser) {
767                 mInternalApplicationNameUpdate = true;
768                 mApplicationNameField.setText(appName);
769                 mInternalApplicationNameUpdate = false;
770             }
771
772             if (!mPackageNameModifiedByUser) {
773                 mInternalPackageNameUpdate = true;
774                 packageName += ".test";  //$NON-NLS-1$
775                 mPackageNameField.setText(packageName);
776                 mInternalPackageNameUpdate = false;
777             }
778
779             if (!mSdkTargetModifiedByUser) {
780                 mInternalSdkTargetUpdate = true;
781                 mSdkTargetSelector.setSelection(mMainInfo.getSdkTarget());
782                 mInternalSdkTargetUpdate = false;
783             }
784
785             if (!mMinSdkVersionModifiedByUser) {
786                 mInternalMinSdkVersionUpdate = true;
787                 mMinSdkVersionField.setText(mMainInfo.getMinSdkVersion());
788                 mInternalMinSdkVersionUpdate = false;
789             }
790         }
791     }
792
793     /**
794      * Callback invoked when the user edits the project text field.
795      */
796     private void onProjectFieldUpdated() {
797         String project = mTestedProjectNameField.getText();
798
799         // Is this a valid project?
800         IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/);
801         for (IJavaProject p : projects) {
802             if (p.getProject().getName().equals(project)) {
803                 setExistingProject(p.getProject());
804                 return;
805             }
806         }
807     }
808
809     /**
810      * Callback called when the user uses the "Browse Projects" button.
811      */
812     private void onProjectBrowse() {
813         IJavaProject p = mProjectChooserHelper.chooseJavaProject(mTestedProjectNameField.getText(),
814                 null /*message*/);
815         if (p != null) {
816             setExistingProject(p.getProject());
817             mTestedProjectNameField.setText(mExistingTestedProject.getName());
818         }
819     }
820
821     private void setExistingProject(IProject project) {
822         mExistingTestedProject = project;
823
824         // Try to update the application, package, sdk target and minSdkVersion accordingly
825         if (project != null &&
826                 (!mApplicationNameModifiedByUser ||
827                  !mPackageNameModifiedByUser     ||
828                  !mSdkTargetModifiedByUser       ||
829                  !mMinSdkVersionModifiedByUser)) {
830
831             ManifestData manifestData = AndroidManifestHelper.parseForData(project);
832             if (manifestData != null) {
833                 String appName = String.format("%1$sTest", project.getName());
834                 String packageName = manifestData.getPackage();
835                 String minSdkVersion = manifestData.getMinSdkVersionString();
836                 IAndroidTarget sdkTarget = null;
837                 if (Sdk.getCurrent() != null) {
838                     sdkTarget = Sdk.getCurrent().getTarget(project);
839                 }
840
841                 if (packageName == null) {
842                     packageName = "";  //$NON-NLS-1$
843                 }
844                 mLastExistingPackageName = packageName;
845
846                 if (!mProjectNameModifiedByUser) {
847                     mInternalProjectNameUpdate = true;
848                     mProjectNameField.setText(appName);
849                     mInternalProjectNameUpdate = false;
850                 }
851
852                 if (!mApplicationNameModifiedByUser) {
853                     mInternalApplicationNameUpdate = true;
854                     mApplicationNameField.setText(appName);
855                     mInternalApplicationNameUpdate = false;
856                 }
857
858                 if (!mPackageNameModifiedByUser) {
859                     mInternalPackageNameUpdate = true;
860                     packageName += ".test";  //$NON-NLS-1$
861                     mPackageNameField.setText(packageName);  //$NON-NLS-1$
862                     mInternalPackageNameUpdate = false;
863                 }
864
865                 if (!mSdkTargetModifiedByUser && sdkTarget != null) {
866                     mInternalSdkTargetUpdate = true;
867                     mSdkTargetSelector.setSelection(sdkTarget);
868                     mInternalSdkTargetUpdate = false;
869                 }
870
871                 if (!mMinSdkVersionModifiedByUser) {
872                     mInternalMinSdkVersionUpdate = true;
873                     if (minSdkVersion != null) {
874                         mMinSdkVersionField.setText(minSdkVersion);
875                     }
876                     if (sdkTarget == null) {
877                         updateSdkSelectorToMatchMinSdkVersion();
878                     }
879                     mInternalMinSdkVersionUpdate = false;
880                 }
881             }
882         }
883
884         updateTestTargetPackageField(mLastExistingPackageName);
885         validatePageComplete();
886     }
887
888     /**
889      * Display a directory browser and update the location path field with the selected path
890      */
891     private void onOpenDirectoryBrowser() {
892
893         String existing_dir = getLocationPathFieldValue();
894
895         // Disable the path if it doesn't exist
896         if (existing_dir.length() == 0) {
897             existing_dir = null;
898         } else {
899             File f = new File(existing_dir);
900             if (!f.exists()) {
901                 existing_dir = null;
902             }
903         }
904
905         DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell());
906         dd.setMessage("Browse for folder");
907         dd.setFilterPath(existing_dir);
908         String abs_dir = dd.open();
909
910         if (abs_dir != null) {
911             updateLocationPathField(abs_dir);
912             validatePageComplete();
913         }
914     }
915
916     /**
917      * Callback when the "create test project" checkbox is changed.
918      * It enables or disables all UI groups accordingly.
919      */
920     private void onCreateTestProjectToggle() {
921         boolean enabled = mInfo.getCreateTestProject();
922         for (Composite c : mToggleComposites) {
923             enableControl(c, enabled);
924         }
925         mSdkTargetSelector.setEnabled(enabled);
926
927         if (enabled) {
928             useMainProjectInformation();
929         }
930         validatePageComplete();
931     }
932
933     /** Enables or disables controls; recursive for composite controls. */
934     private void enableControl(Control c, boolean enabled) {
935         c.setEnabled(enabled);
936         if (c instanceof Composite)
937         for (Control c2 : ((Composite) c).getChildren()) {
938             enableControl(c2, enabled);
939         }
940     }
941
942     /**
943      * Enables or disable the location widgets depending on the user selection:
944      * the location path is enabled when using the "existing source" mode (i.e. not new project)
945      * or in new project mode with the "use default location" turned off.
946      */
947     private void enableLocationWidgets() {
948         boolean use_default = mInfo.useDefaultLocation();
949         boolean location_enabled = !use_default;
950
951         mLocationLabel.setEnabled(location_enabled);
952         mLocationPathField.setEnabled(location_enabled);
953         mBrowseButton.setEnabled(location_enabled);
954
955         updateLocationPathField(null);
956     }
957
958     /**
959      * Updates the location directory path field.
960      * <br/>
961      * When custom user selection is enabled, use the abs_dir argument if not null and also
962      * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the
963      * user selection to be remembered when the user switches from default to custom.
964      * <br/>
965      * When custom user selection is disabled, use the workspace default location with the
966      * current project name. This does not change the internally cached abs_dir.
967      *
968      * @param abs_dir A new absolute directory path or null to use the default.
969      */
970     private void updateLocationPathField(String abs_dir) {
971         boolean use_default = mInfo.useDefaultLocation();
972         boolean custom_location = !use_default;
973
974         if (!mInternalLocationPathUpdate) {
975             mInternalLocationPathUpdate = true;
976             if (custom_location) {
977                 if (abs_dir != null) {
978                     // We get here if the user selected a directory with the "Browse" button.
979                     sCustomLocationOsPath = TextProcessor.process(abs_dir);
980                 }
981                 if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) {
982                     mLocationPathField.setText(sCustomLocationOsPath);
983                 }
984             } else {
985                 String value = Platform.getLocation().append(mInfo.getProjectName()).toString();
986                 value = TextProcessor.process(value);
987                 if (!mLocationPathField.getText().equals(value)) {
988                     mLocationPathField.setText(value);
989                 }
990             }
991             validatePageComplete();
992             mInternalLocationPathUpdate = false;
993         }
994     }
995
996     /**
997      * The location path field is either modified internally (from updateLocationPathField)
998      * or manually by the user when the custom_location mode is not set.
999      *
1000      * Ignore the internal modification. When modified by the user, memorize the choice and
1001      * validate the page.
1002      */
1003     private void onLocationPathFieldModified() {
1004         if (!mInternalLocationPathUpdate) {
1005             // When the updates doesn't come from updateLocationPathField, it must be the user
1006             // editing the field manually, in which case we want to save the value internally
1007             String newPath = getLocationPathFieldValue();
1008             sCustomLocationOsPath = newPath;
1009             validatePageComplete();
1010         }
1011     }
1012
1013     /**
1014      * The package name field is either modified internally (from extractNamesFromAndroidManifest)
1015      * or manually by the user when the custom_location mode is not set.
1016      *
1017      * Ignore the internal modification. When modified by the user, memorize the choice and
1018      * validate the page.
1019      */
1020     private void onPackageNameFieldModified() {
1021         updateTestTargetPackageField(null);
1022         validatePageComplete();
1023     }
1024
1025     /**
1026      * Changes the {@link #mTestTargetPackageLabel} field.
1027      *
1028      * When using the "self-test" option, the packageName argument is ignored and the
1029      * current value from the project package is used.
1030      *
1031      * Otherwise the packageName is used if it is not null.
1032      */
1033     private void updateTestTargetPackageField(String packageName) {
1034         if (mInfo.isTestingSelf()) {
1035             mTestTargetPackageLabel.setText(mInfo.getPackageName());
1036
1037         } else if (packageName != null) {
1038             mTestTargetPackageLabel.setText(packageName);
1039         }
1040     }
1041
1042     /**
1043      * Called when the min sdk version field has been modified.
1044      *
1045      * Ignore the internal modifications. When modified by the user, try to match
1046      * a target with the same API level.
1047      */
1048     private void onMinSdkVersionFieldModified() {
1049         if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
1050             return;
1051         }
1052
1053         updateSdkSelectorToMatchMinSdkVersion();
1054
1055         mMinSdkVersionModifiedByUser = true;
1056     }
1057
1058     /**
1059      * Try to find an SDK Target that matches the current MinSdkVersion.
1060      *
1061      * There can be multiple targets with the same sdk api version, so don't change
1062      * it if it's already at the right version. Otherwise pick the first target
1063      * that matches.
1064      */
1065     private void updateSdkSelectorToMatchMinSdkVersion() {
1066         String minSdkVersion = mInfo.getMinSdkVersion();
1067
1068         IAndroidTarget curr_target = mInfo.getSdkTarget();
1069         if (curr_target != null && curr_target.getVersion().equals(minSdkVersion)) {
1070             return;
1071         }
1072
1073         for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
1074             if (target.getVersion().equals(minSdkVersion)) {
1075                 mSdkTargetSelector.setSelection(target);
1076                 return;
1077             }
1078         }
1079     }
1080
1081     /**
1082      * Called when an SDK target is modified.
1083      *
1084      * Also changes the minSdkVersion field to reflect the sdk api level that has
1085      * just been selected.
1086      */
1087     private void onSdkTargetModified() {
1088         if (mInternalMinSdkVersionUpdate || mInternalSdkTargetUpdate) {
1089             return;
1090         }
1091
1092         IAndroidTarget target = mInfo.getSdkTarget();
1093
1094         if (target != null) {
1095             mInternalMinSdkVersionUpdate = true;
1096             mMinSdkVersionField.setText(target.getVersion().getApiString());
1097             mInternalMinSdkVersionUpdate = false;
1098         }
1099
1100         mSdkTargetModifiedByUser = true;
1101     }
1102
1103     /**
1104      * Returns whether this page's controls currently all contain valid values.
1105      *
1106      * @return <code>true</code> if all controls are valid, and
1107      *         <code>false</code> if at least one is invalid
1108      */
1109     private boolean validatePage() {
1110         IWorkspace workspace = ResourcesPlugin.getWorkspace();
1111
1112         int status = MSG_NONE;
1113
1114         // there is nothing to validate if we're not going to create a test project
1115         if (mInfo.getCreateTestProject()) {
1116             status = validateProjectField(workspace);
1117             if ((status & MSG_ERROR) == 0) {
1118                 status |= validateLocationPath(workspace);
1119             }
1120             if ((status & MSG_ERROR) == 0) {
1121                 status |= validateTestTarget();
1122             }
1123             if ((status & MSG_ERROR) == 0) {
1124                 status |= validateSdkTarget();
1125             }
1126             if ((status & MSG_ERROR) == 0) {
1127                 status |= validatePackageField();
1128             }
1129             if ((status & MSG_ERROR) == 0) {
1130                 status |= validateMinSdkVersionField();
1131             }
1132         }
1133         if (status == MSG_NONE)  {
1134             setStatus(null, MSG_NONE);
1135         }
1136
1137         // Return false if there's an error so that the finish button be disabled.
1138         return (status & MSG_ERROR) == 0;
1139     }
1140
1141     /**
1142      * Validates the page and updates the Next/Finish buttons
1143      */
1144     private void validatePageComplete() {
1145         setPageComplete(validatePage());
1146     }
1147
1148     /**
1149      * Validates the test target (self, main project or existing project)
1150      *
1151      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1152      */
1153     private int validateTestTarget() {
1154         if (mInfo.isTestingExisting() && mInfo.getExistingTestedProject() == null) {
1155             return setStatus("Please select an existing Android project as a test target.",
1156                     MSG_ERROR);
1157         }
1158
1159         return MSG_NONE;
1160     }
1161
1162     /**
1163      * Validates the project name field.
1164      *
1165      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1166      */
1167     private int validateProjectField(IWorkspace workspace) {
1168         // Validate project field
1169         String projectName = mInfo.getProjectName();
1170         if (projectName.length() == 0) {
1171             return setStatus("Project name must be specified", MSG_ERROR);
1172         }
1173
1174         // Limit the project name to shell-agnostic characters since it will be used to
1175         // generate the final package
1176         if (!sProjectNamePattern.matcher(projectName).matches()) {
1177             return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.",
1178                     MSG_ERROR);
1179         }
1180
1181         IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT);
1182         if (!nameStatus.isOK()) {
1183             return setStatus(nameStatus.getMessage(), MSG_ERROR);
1184         }
1185
1186         if (mMainInfo != null && projectName.equals(mMainInfo.getProjectName())) {
1187             return setStatus("The main project name and the test project name must be different.",
1188                     MSG_ERROR);
1189         }
1190
1191         if (getProjectHandle().exists()) {
1192             return setStatus("A project with that name already exists in the workspace",
1193                     MSG_ERROR);
1194         }
1195
1196         return MSG_NONE;
1197     }
1198
1199     /**
1200      * Validates the location path field.
1201      *
1202      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1203      */
1204     private int validateLocationPath(IWorkspace workspace) {
1205         Path path = new Path(getProjectLocation());
1206         if (!mInfo.useDefaultLocation()) {
1207             // If not using the default value validate the location.
1208             URI uri = URIUtil.toURI(path.toOSString());
1209             IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(),
1210                     uri);
1211             if (!locationStatus.isOK()) {
1212                 return setStatus(locationStatus.getMessage(), MSG_ERROR);
1213             } else {
1214                 // The location is valid as far as Eclipse is concerned (i.e. mostly not
1215                 // an existing workspace project.) Check it either doesn't exist or is
1216                 // a directory that is empty.
1217                 File f = path.toFile();
1218                 if (f.exists() && !f.isDirectory()) {
1219                     return setStatus("A directory name must be specified.", MSG_ERROR);
1220                 } else if (f.isDirectory()) {
1221                     // However if the directory exists, we should put a warning if it is not
1222                     // empty. We don't put an error (we'll ask the user again for confirmation
1223                     // before using the directory.)
1224                     String[] l = f.list();
1225                     if (l.length != 0) {
1226                         return setStatus("The selected output directory is not empty.",
1227                                 MSG_WARNING);
1228                     }
1229                 }
1230             }
1231         } else {
1232             // Otherwise validate the path string is not empty
1233             if (getProjectLocation().length() == 0) {
1234                 return setStatus("A directory name must be specified.", MSG_ERROR);
1235             }
1236
1237             File dest = path.append(mInfo.getProjectName()).toFile();
1238             if (dest.exists()) {
1239                 return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.",
1240                         mInfo.getProjectName()), MSG_ERROR);
1241             }
1242         }
1243
1244         return MSG_NONE;
1245     }
1246
1247     /**
1248      * Validates the sdk target choice.
1249      *
1250      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1251      */
1252     private int validateSdkTarget() {
1253         if (mInfo.getSdkTarget() == null) {
1254             return setStatus("An SDK Target must be specified.", MSG_ERROR);
1255         }
1256         return MSG_NONE;
1257     }
1258
1259     /**
1260      * Validates the sdk target choice.
1261      *
1262      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1263      */
1264     private int validateMinSdkVersionField() {
1265
1266         // If the min sdk version is empty, it is always accepted.
1267         if (mInfo.getMinSdkVersion().length() == 0) {
1268             return MSG_NONE;
1269         }
1270
1271         if (mInfo.getSdkTarget() != null &&
1272                 mInfo.getSdkTarget().getVersion().equals(mInfo.getMinSdkVersion()) == false) {
1273             return setStatus("The API level for the selected SDK target does not match the Min SDK version.",
1274                     mInfo.getSdkTarget().getVersion().isPreview() ? MSG_ERROR : MSG_WARNING);
1275         }
1276
1277         return MSG_NONE;
1278     }
1279
1280     /**
1281      * Validates the package name field.
1282      *
1283      * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
1284      */
1285     private int validatePackageField() {
1286         // Validate package field
1287         String packageName = mInfo.getPackageName();
1288         if (packageName.length() == 0) {
1289             return setStatus("Project package name must be specified.", MSG_ERROR);
1290         }
1291
1292         // Check it's a valid package string
1293         int result = MSG_NONE;
1294         IStatus status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
1295         if (!status.isOK()) {
1296             result = setStatus(String.format("Project package: %s", status.getMessage()),
1297                         status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
1298         }
1299
1300         // The Android Activity Manager does not accept packages names with only one
1301         // identifier. Check the package name has at least one dot in them (the previous rule
1302         // validated that if such a dot exist, it's not the first nor last characters of the
1303         // string.)
1304         if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
1305             return setStatus("Project package name must have at least two identifiers.", MSG_ERROR);
1306         }
1307
1308         // Check that the target package name is valid too
1309         packageName = mInfo.getTargetPackageName();
1310         if (packageName.length() == 0) {
1311             return setStatus("Target package name must be specified.", MSG_ERROR);
1312         }
1313
1314         // Check it's a valid package string
1315         status = JavaConventions.validatePackageName(packageName, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$
1316         if (!status.isOK()) {
1317             result = setStatus(String.format("Target package: %s", status.getMessage()),
1318                         status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING);
1319         }
1320
1321         if (result != MSG_ERROR && packageName.indexOf('.') == -1) {
1322             return setStatus("Target name must have at least two identifiers.", MSG_ERROR);
1323         }
1324
1325         return result;
1326     }
1327
1328     /**
1329      * Sets the error message for the wizard with the given message icon.
1330      *
1331      * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING.
1332      * @return As a convenience, always returns messageType so that the caller can return
1333      *         immediately.
1334      */
1335     private int setStatus(String message, int messageType) {
1336         if (message == null) {
1337             setErrorMessage(null);
1338             setMessage(null);
1339         } else if (!message.equals(getMessage())) {
1340             setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR);
1341         }
1342         return messageType;
1343     }
1344
1345 }