Java tutorial
/******************************************************************************* * Copyright 2011 Google Inc. All Rights Reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.google.gdt.eclipse.mobile.android.wizards; import com.google.appengine.eclipse.core.preferences.GaePreferences; import com.google.appengine.eclipse.core.preferences.ui.GaePreferencePage; import com.google.appengine.eclipse.core.sdk.GaeSdk; import com.google.appengine.eclipse.core.sdk.GaeSdkContainer; import com.google.gdt.eclipse.appengine.rpc.AppEngineRPCPlugin; import com.google.gdt.eclipse.core.sdk.Sdk; import com.google.gdt.eclipse.core.sdk.SdkClasspathContainer; import com.android.sdklib.IAndroidTarget; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.JavaConventions; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.window.Window; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.dialogs.PreferencesUtil; import java.io.File; import java.net.URI; import java.util.regex.Pattern; /** * Wizard page where the user specifies the parameters for a new Android and App * Engine projects. */ @SuppressWarnings("restriction") public class NewAndroidCloudProjectWizardPage extends WizardPage { private static final String ANDROID_PREFERENCE_ID = "com.android.ide.eclipse.preferences.main"; //$NON-NLS-1$ // Initial value for the Create Activity check box. private static final boolean INITIAL_CREATE_ACTIVITY = true; /** * Initial value for all name fields (project, activity, application, * package). Used whenever a value is requested before controls are created. */ private static final String INITIAL_NAME = ""; //$NON-NLS-1$ // Initial value for the Use Default Location check box. private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; /** * Pattern for characters accepted in a project name. Since this will be used * as a directory name, we're being a bit conservative on purpose. It cannot * start with a space. */ private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$ private static final int MSG_ERROR = 2; private static final int MSG_NONE = 0; private static final int MSG_WARNING = 1; public static boolean isAndroidSdkInstalled() { return com.android.ide.eclipse.adt.internal.sdk.Sdk.getCurrent() != null; } // widgets - Android project private Text androidProjectNameText; private Button browseButton; private Link configureAndroidSdkLink; private Link configureOrDownloadLink; private Button createActivityCheck; private Label locationLabel; private Text locationPathText; private Text packageNameText; private IAndroidTarget target = null; private boolean useDefaultLocation = true; private Button useDefaultLocationButton; /** * Create the wizard. */ public NewAndroidCloudProjectWizardPage() { super("New App Engine Connected Android Project"); setTitle("App Engine Connected Android Project"); setDescription("Create a sample Android project and a Google App Engine Server project"); } @Override public boolean canFlipToNextPage() { return false; } /** * Create contents of the wizard. * * @param parent */ public void createControl(Composite parent) { initializeDialogUnits(parent); final Composite container = new Composite(parent, SWT.NULL); container.setFont(parent.getFont()); container.setLayout(new GridLayout()); container.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL)); createInfoSection(container); Label fillerLabel = new Label(container, SWT.NONE); createProjectNameLocationGroup(container); createGoogleSdkGroup(container); validateSdkTarget(); validatePageComplete(); setControl(container); } public String getActivityName() { String name = getAndroidProjectName(); return name.substring(0, 1).toUpperCase() + name.substring(1) + "Activity"; } public String getAndroidApplicationName() { String name = getAndroidProjectName(); return name.substring(0, 1).toUpperCase() + name.substring(1); } public String getAndroidPackageName() { return packageNameText.getText().trim(); } public String getAndroidProjectName() { return androidProjectNameText == null ? INITIAL_NAME : androidProjectNameText.getText().trim(); } /** * Returns the most recent version of the target. */ public IAndroidTarget getAndroidSdkTarget() { if (target != null) { return target; } IAndroidTarget[] targets = null; targets = getAndroidSdks(); if (targets == null) { return null; } target = targets[0]; if (targets.length > 1) { for (int i = 1; i < targets.length; i++) { if (targets[i].isPlatform() && targets[i].getVersion().getApiLevel() > 7) { if (target.getVersion().getApiLevel() < 8 || target.getVersion().compareTo(targets[i].getVersion()) > 0) { target = targets[i]; } } } } return target; } public URI getCreationLocationURI() { if (useDefaultLocationButton.getSelection()) { return ResourcesPlugin.getWorkspace().getRoot().getLocationURI(); } return new Path(getOutputDirectory()).toFile().toURI(); } public String getGaePackageName() { return getAndroidPackageName(); } public String getGaeProjectName() { return getAndroidProjectName(); } /** * Returns the current project location path as entered by the user, or its * anticipated initial value. Note that if the default has been returned the * path in a project description used to create a project should not be set. * * @return the project location path or its anticipated initial value. */ public IPath getLocationPath() { return new Path(getProjectLocation()); } public String getMinSdkVersion() { // C2DM is supported by api 8 and above return "8"; } public GaeSdk getSelectedGaeSdk() { return GaePreferences.getDefaultSdk(); } // Returns the value of the "Create Activity" checkbox. public boolean isCreateActivity() { return createActivityCheck == null ? INITIAL_CREATE_ACTIVITY : createActivityCheck.getSelection(); } public void setPackageName(String packageName) { packageNameText.setText(packageName); } private void createConfigureAdtSdkLink(Composite parent) { configureAndroidSdkLink = new Link(parent, SWT.NONE); configureAndroidSdkLink.setText("<a href=\"#\">" + "Configure Android SDK ..." + "</a>"); configureAndroidSdkLink.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { if (Window.OK == PreferencesUtil.createPreferenceDialogOn(getShell(), ANDROID_PREFERENCE_ID, new String[] { ANDROID_PREFERENCE_ID }, null).open()) { updateControls(); validatePageComplete(); } } }); configureAndroidSdkLink.setEnabled(true); } /** * Create link to configure App Engine SDK, if there is no default */ private void createConfigureGaeSdkLink(Composite parent) { configureOrDownloadLink = new Link(parent, SWT.NONE); configureOrDownloadLink.setText("<a href=\"#\">" + "Configure App Engine SDK ..." + "</a>"); configureOrDownloadLink.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent e) { if (Window.OK == PreferencesUtil.createPreferenceDialogOn(getShell(), GaePreferencePage.ID, new String[] { GaePreferencePage.ID }, null).open()) { updateControls(); validatePageComplete(); } } }); configureOrDownloadLink.setEnabled(true); } /** * Creates the group for Gae/ADT SDK Selection */ private void createGoogleSdkGroup(Composite container) { if (getSelectedGaeSdk() == null) { createConfigureGaeSdkLink(container); } if (!isAndroidSdkInstalled()) { createConfigureAdtSdkLink(container); } } private void createInfoSection(final Composite container) { Label infoLabel = new Label(container, SWT.WRAP); infoLabel.setText("Creates a sample application that includes an Android mobile client," + " and backend service using cloud endpoints that runs on App Engine. This sample " + "also includes code that allows backend services to communicate with the Android " + "client efficiently using the Google Cloud Messaging Framework (GCM)."); GridData infoData = new GridData(GridData.FILL_HORIZONTAL); infoData.widthHint = 200; infoLabel.setLayoutData(infoData); } /** * Creates the group for the Android project name: [label: "Project Name"] * [text field] * * @param parent the parent composite */ private final void createProjectNameLocationGroup(Composite parent) { Composite group = new Composite(parent, SWT.NONE); group.setLayout(new GridLayout(2, false)); group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); // new project label Label label = new Label(group, SWT.NONE); label.setText("Project name:"); label.setFont(parent.getFont()); label.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); // new project name entry field androidProjectNameText = new Text(group, SWT.BORDER); androidProjectNameText.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); androidProjectNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); androidProjectNameText.setFont(parent.getFont()); androidProjectNameText.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { updateFields(androidProjectNameText.getText().trim()); validatePageComplete(); } }); label = new Label(group, SWT.NONE); label.setText("Package name:"); label.setFont(parent.getFont()); label.setToolTipText( "Namespace of the Package to create. This must be a Java namespace with at least two components."); // new package name entry field packageNameText = new Text(group, SWT.BORDER); GridData data = new GridData(GridData.FILL_HORIZONTAL); packageNameText.setToolTipText( "Namespace of the Package to create. This must be a Java namespace with at least two components."); packageNameText.setLayoutData(data); packageNameText.setFont(parent.getFont()); packageNameText.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { validatePageComplete(); } }); useDefaultLocationButton = new Button(group, SWT.CHECK); useDefaultLocationButton.setText("Use default location"); useDefaultLocationButton.setSelection(INITIAL_USE_DEFAULT_LOCATION); useDefaultLocationButton.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false, 2, 1)); SelectionListener locationListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); useDefaultLocation = useDefaultLocationButton.getSelection(); updateControls(); validatePageComplete(); } }; useDefaultLocationButton.addSelectionListener(locationListener); Composite locationGroup = new Composite(group, SWT.NONE); locationGroup.setLayout(new GridLayout(3, /* num columns */ false /* columns of not equal size */)); locationGroup.setLayoutData(new GridData(SWT.FILL, GridData.BEGINNING, true, false, 2, 1)); locationGroup.setFont(parent.getFont()); locationLabel = new Label(locationGroup, SWT.NONE); locationLabel.setText("Location:"); locationPathText = new Text(locationGroup, SWT.BORDER);/* verticalSpan */ locationPathText.setLayoutData(new GridData(SWT.FILL, GridData.BEGINNING, true, false, 1, 1)); locationPathText.setFont(parent.getFont()); String outDir = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString(); locationPathText.setText(outDir); locationPathText.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { validatePageComplete(); } }); locationPathText.setEnabled(false); browseButton = new Button(locationGroup, SWT.PUSH); browseButton.setText("Browse..."); setButtonLayoutData(browseButton); browseButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { String dd = onOpenDirectoryBrowser(); if (dd != null) { locationPathText.setText(dd); validatePageComplete(); } } }); browseButton.setEnabled(false); } /** * @param targets * @return */ private IAndroidTarget[] getAndroidSdks() { if (isAndroidSdkInstalled()) { return com.android.ide.eclipse.adt.internal.sdk.Sdk.getCurrent().getTargets(); } return null; } private String getOutputDirectory() { return locationPathText.getText().trim(); } /** * Creates a project resource handle for the current project name field value. * * @param projectName * @return the new project resource handle */ private IProject getProjectHandle(String projectName) { return ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); } /** * Returns the current project location, depending on the Use Default Location * check box or the Create From Sample check box. */ private String getProjectLocation() { if (useDefaultLocation) { return Platform.getLocation().toString(); } else { return locationPathText.getText().trim(); } } /** * Display a directory browser and update the location path field with the * selected path */ private String onOpenDirectoryBrowser() { String existingDir = locationPathText == null ? "" : locationPathText.getText().trim(); // Disable the path if it doesn't exist if (existingDir.length() == 0) { existingDir = null; } else { File f = new File(existingDir); if (!f.exists()) { existingDir = null; } } DirectoryDialog dd = new DirectoryDialog(locationPathText.getShell()); dd.setMessage("Browse for folder"); dd.setFilterPath(existingDir); String absDir = dd.open(); if (absDir != null) { return absDir; } return null; } private boolean projectExists(String projectName) { return getProjectHandle(projectName).exists(); } /** * Sets the error message for the wizard with the given message icon. * * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING. * @return As a convenience, always returns messageType so that the caller can * return immediately. */ private int setStatus(String message, int messageType) { if (message == null) { setErrorMessage(null); setMessage(null); } else if (!message.equals(getMessage())) { if (messageType == MSG_NONE) { setMessage(message, IMessageProvider.NONE); } else if (messageType == MSG_ERROR) { setMessage(message, IMessageProvider.ERROR); } else { setMessage(message, IMessageProvider.WARNING); } } return messageType; } /** * Update the various controls */ private void updateControls() { // Set the output directory to the workspace if (useDefaultLocationButton.getSelection()) { locationLabel.setEnabled(false); locationPathText.setEnabled(false); browseButton.setEnabled(false); } else { locationLabel.setEnabled(true); locationPathText.setEnabled(true); browseButton.setEnabled(true); } } /** * Updates the fields in the wizard based on project name */ private void updateFields(String name) { packageNameText.setText("com." + name.toLowerCase().replaceAll(" ", "")); if (useDefaultLocation) { String outDir = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString(); if (getAndroidProjectName().length() > 0) { outDir += (System.getProperty("file.separator") + getAndroidProjectName()); } locationPathText.setText(outDir); } } /** * Validates the location path field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateLocationPath(IWorkspace workspace) { Path path = new Path(getProjectLocation()); if (!useDefaultLocation) { // If not using the default value validate the location. URI uri = URIUtil.toURI(path.toOSString()); IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(getAndroidProjectName()), uri); if (!locationStatus.isOK()) { return setStatus(locationStatus.getMessage(), MSG_ERROR); } else { // The location is valid as far as Eclipse is concerned (i.e. mostly not // an existing workspace project.) Check it either doesn't exist or is // a directory that is empty. File f = path.toFile(); if (f.exists() && !f.isDirectory()) { return setStatus("A directory name must be specified.", MSG_ERROR); } else if (f.isDirectory()) { // However if the directory exists, we should put a warning if it is // not // empty. We don't put an error (we'll ask the user again for // confirmation // before using the directory.) String[] l = f.list(); if (l.length != 0) { return setStatus("The selected output directory is not empty.", MSG_WARNING); } } } } else { // Otherwise validate the path string is not empty if (getProjectLocation().length() == 0) { return setStatus("A directory name must be specified.", MSG_ERROR); } if (getAndroidProjectName().length() > 0) { File dest = path.append(getAndroidProjectName()).toFile(); if (dest.exists()) { return setStatus(String.format( "There is already a file or directory named \"%1$s\" in the selected location.", getAndroidProjectName()), MSG_ERROR); } } } return MSG_NONE; } /** * Validates the package name field. * * @param packageFieldContents * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validatePackageField(String packageFieldContents) { // Validate package field if (packageFieldContents.length() == 0) { return setStatus("Package name must be specified.", MSG_ERROR); } // Check it's a valid package string int result = MSG_NONE; IStatus status = JavaConventions.validatePackageName(packageFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ if (!status.isOK()) { result = setStatus(status.getMessage(), status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); } // The Android Activity Manager does not accept packages names with only one // identifier. Check the package name has at least one dot in them (the // previous rule // validated that if such a dot exist, it's not the first nor last // characters of the // string.) if (result != MSG_ERROR && packageFieldContents.indexOf('.') == -1) { return setStatus("Package name must have at least two identifiers.", MSG_ERROR); } return result; } /** * Returns whether this page's controls currently all contain valid values. * * @return <code>true</code> if all controls are valid, and <code>false</code> * if at least one is invalid */ private boolean validatePage() { IWorkspace workspace = ResourcesPlugin.getWorkspace(); int status = validateProjectField(workspace, getAndroidProjectName()); if (getAndroidProjectName().length() == 0) { return false; } if ((status & MSG_ERROR) == 0) { status |= validateLocationPath(workspace); } if ((status & MSG_ERROR) == 0) { status |= validatePackageField(getAndroidPackageName()); } if ((status & MSG_ERROR) == 0) { status |= validateSdks(); } if (status == MSG_NONE) { setStatus(null, MSG_NONE); } // Return false if there's an error so that the finish button be disabled. return (status & MSG_ERROR) == 0; } /** * Validates the page and updates the Next/Finish buttons */ private void validatePageComplete() { setPageComplete(validatePage()); } /** * Validates the project name field. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateProjectField(IWorkspace workspace, String projectName) { if (projectName.length() == 0) { return setStatus("Enter a project name", MSG_NONE); } // Limit the project name to shell-agnostic characters since it will be used // to generate the final package if (!sProjectNamePattern.matcher(projectName).matches()) { return setStatus( "The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.", MSG_ERROR); } IStatus nameStatus = workspace.validateName(projectName, IResource.PROJECT); if (!nameStatus.isOK()) { return setStatus(nameStatus.getMessage(), MSG_ERROR); } if (projectExists(projectName)) { return setStatus("A project with the name " + projectName + " already exists in the workspace", MSG_ERROR); } if (projectExists(projectName + AppEngineRPCPlugin.GAE_PROJECT_NAME_SUFFIX)) { return setStatus("A project with the name " + projectName + AppEngineRPCPlugin.GAE_PROJECT_NAME_SUFFIX + " already exists in the workspace", MSG_ERROR); } return MSG_NONE; } /** * Validates the Gae and Android Sdk selections * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateSdks() { Sdk selectedGaeSdk = getSelectedGaeSdk(); if (selectedGaeSdk == null) { return setStatus("Please configure an App Engine SDK.", MSG_ERROR); } IStatus gaeSdkValidationStatus = selectedGaeSdk.validate(); if (!gaeSdkValidationStatus.isOK()) { return setStatus("The selected App Engine SDK is not valid: " + gaeSdkValidationStatus.getMessage(), MSG_ERROR); } if (!isAndroidSdkInstalled()) { return setStatus("Please configure an Android SDK.", MSG_ERROR); } return MSG_NONE; } /** * Validates the sdk target choice. * * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. */ private int validateSdkTarget() { if (isAndroidSdkInstalled()) { return MSG_NONE; } return setStatus("An Android SDK Target must be specified.", MSG_ERROR); } IPath getGaeSdkContainerPath() { return getSdkContainerPath(getSelectedGaeSdk(), GaeSdkContainer.CONTAINER_ID); } IPath getSdkContainerPath(Sdk sdkSelection, String containerId) { if (sdkSelection != null) { return SdkClasspathContainer.computeContainerPath(containerId, sdkSelection, SdkClasspathContainer.Type.DEFAULT); } return null; } }