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.gwt.eclipse.core.runtime; import com.google.gdt.eclipse.core.ClasspathUtilities; import com.google.gdt.eclipse.core.StatusUtilities; import com.google.gdt.eclipse.core.extensions.ExtensionQuery; import com.google.gdt.eclipse.core.sdk.AbstractSdk; import com.google.gdt.eclipse.core.sdk.SdkFactory; import com.google.gdt.eclipse.core.sdk.SdkUtils; import com.google.gwt.eclipse.core.GWTPlugin; import com.google.gwt.eclipse.core.GWTPluginLog; import com.google.gwt.eclipse.core.launch.processors.GwtLaunchConfigurationProcessorUtilities; import com.google.gwt.eclipse.core.preferences.GWTPreferences; import com.google.gwt.eclipse.core.util.Util; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.variables.IStringVariableManager; import org.eclipse.core.variables.IValueVariable; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.launching.JavaRuntime; import java.io.File; import java.io.FileFilter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Represents a GWT runtime and provides a URLClassLoader that can be used to load the gwt-user and * gwt-dev classes. * * TODO: Move this and subtypes into the sdk package. */ public abstract class GWTRuntime extends AbstractSdk { /** * A factory that returns a project-bound GWT SDK. Extension points can implement this interface * to return an externally-computed project-bound GWT SDK in response to calls to * {@link GWTRuntime#findSdkFor(IJavaProject)}. */ public interface IProjectBoundSdkFactory { ProjectBoundSdk newInstance(IJavaProject javaProject); } /** * Models an {@link com.google.gdt.eclipse.core.sdk.Sdk} that was detected on a project's * classpath. */ public static class ProjectBoundSdk extends GWTRuntime { private static IPath getAbsoluteLocation(IPath workspaceRelativePath, IProject project) { IPath relativeSourcePath = workspaceRelativePath.removeFirstSegments(1); return project.getFolder(relativeSourcePath).getLocation(); } protected final IJavaProject javaProject; protected ProjectBoundSdk(IJavaProject javaProject) { super("", null); this.javaProject = javaProject; } /** * Returns a {@link ClassLoader} that is backed by the project's runtime classpath. * * TODO: This returns a classloader which contains ALL of the jars of the project. Lookups on * this thing are going to be SLOW. Can we optimize this? We could create a classloader that * just contains the jars that GWT requires. Maybe caching is the right solution here. * * TODO: Why can't we just delegate to {@link #getClasspathEntries()} when generating the * classloader URLs? Why do we have to add every URL that is part of the project? That would * certainly speed up lookups on this classloader. Maybe we cannot do this because project-bound * sdks handle the case of source-based runtimes, and in that case, we need all of the * dependencies as part of the classloader. */ @Override public URLClassLoader createClassLoader() throws SdkException, MalformedURLException { try { String[] defaultRuntimeClasspath = JavaRuntime.computeDefaultRuntimeClassPath(javaProject); URL[] urls = new URL[defaultRuntimeClasspath.length]; for (int i = 0; i < defaultRuntimeClasspath.length; ++i) { File file = new File(defaultRuntimeClasspath[i]); urls[i] = file.toURI().toURL(); } return new URLClassLoader(urls); } catch (CoreException e) { throw new SdkException(e); } } /** * Returns the classpath entries from the {@link IJavaProject}'s raw classpath that make up the * {@link com.google.gdt.eclipse.core.sdk.Sdk}. * * TODO: Can we clean up uses of this method? It really only seems to be useful in the case * where you want to derive a classpath container from an SDK. I'm not sure if this should be a * first-class method on an SDK. * * TODO: Get rid of this method. It's only needed when a classpath container needs to be * initialized, and classpath containers are never initialized from ProjectBoundSdks. */ @Override public IClasspathEntry[] getClasspathEntries() { try { // If containers are being used, we avoid duplicates by using a set Set<IClasspathEntry> classpathEntries = new HashSet<IClasspathEntry>(); IClasspathEntry gwtDev = findGwtDevClasspathEntry(); if (gwtDev != null) { classpathEntries.add(gwtDev); } IClasspathEntry gwtUser = findGwtUserClasspathEntry(); if (gwtUser != null) { classpathEntries.add(gwtUser); } IClasspathEntry gwtCodeServer = findGwtCodeServerClasspathEntry(); if (gwtCodeServer != null) { classpathEntries.add(gwtCodeServer); } final List<IClasspathEntry> rawClasspath = Arrays.asList(javaProject.getRawClasspath()); // Sort the classpath entries so they match the declared order of the // raw classpath. IClasspathEntry[] classpathEntryArray = classpathEntries.toArray(NO_ICLASSPATH_ENTRIES); Collections.sort(Arrays.asList(classpathEntryArray), new Comparator<IClasspathEntry>() { @Override public int compare(IClasspathEntry o1, IClasspathEntry o2) { return rawClasspath.indexOf(o1) - rawClasspath.indexOf(o2); } }); return classpathEntryArray; } catch (JavaModelException e) { GWTPluginLog.logError(e); } return AbstractSdk.NO_ICLASSPATH_ENTRIES; } @Override public File getDevJar() throws SdkException, JavaModelException { IPath installPath = computeInstallPath(); if (installPath != null) { return installPath.append(Util.getDevJarName(installPath)).toFile(); } return null; } @Override public IPath getInstallationPath() { if (usesGwtDevProject()) { // Project backed sdks use the workspace location as the install // location. return ResourcesPlugin.getWorkspace().getRoot().getLocation(); } return computeInstallPath(); } @Override public String getName() { IPath installationPath = getInstallationPath(); if (installationPath != null) { return installationPath.toOSString(); } else { return "Unknown"; } } @Override public File[] getWebAppClasspathFiles(IProject project) { IPath installPath = computeInstallPath(); if (installPath != null) { return new File[] { installPath.append("gwt-servlet.jar").toFile() }; } return NO_FILES; } @Override public boolean usesGwtDevProject() { try { IClasspathEntry[] resolvedClasspath = javaProject.getResolvedClasspath(true); for (IClasspathEntry resolvedClasspathEntry : resolvedClasspath) { String projectName = resolvedClasspathEntry.getPath().segment(0); if (projectName.equals(GWTProjectsRuntime.GWT_DEV_FALLBACK_PROJECT_NAME) || projectName.equals(GWTProjectsRuntime.getPlatformSpecificDevProjectName())) { return true; } } } catch (JavaModelException e) { GWTPluginLog.logError(e); } return false; } @Override public IStatus validate() { if (getInstallationPath() == null) { return StatusUtilities.newErrorStatus( "Could not determine an installation path based on the project's classpath.", GWTPlugin.PLUGIN_ID); } if (usesGwtDevProject()) { IStatus status = GWTProjectsRuntime.getGwtDevJarStatus(this); if (!status.isOK()) { return status; } } return StatusUtilities.OK_STATUS; } /** * Returns the value of the gwt_devjar variable or <code>null</code> if the variable is not * defined. */ private IPath computeGwtDevJarVariableValue() { IStringVariableManager variableManager = VariablesPlugin.getDefault().getStringVariableManager(); IValueVariable valueVariable = variableManager.getValueVariable("gwt_devjar"); if (valueVariable != null) { String value = valueVariable.getValue(); if (value != null) { IPath path = new Path(value); return path.removeLastSegments(1); } } return null; } private IPath computeInstallPath() { try { IClasspathEntry classpathEntry = findGwtDevClasspathEntry(); if (classpathEntry == null) { classpathEntry = findGwtUserClasspathEntry(); } if (classpathEntry != null) { return computeInstallPath(classpathEntry); } return null; } catch (JavaModelException e) { GWTPluginLog.logError(e); } return null; } private IPath computeInstallPath(IClasspathEntry classpathEntry) throws JavaModelException { IPath installPath = null; switch (classpathEntry.getEntryKind()) { case IClasspathEntry.CPE_CONTAINER: GWTRuntime sdk = GWTPreferences.getSdkManager().findSdkForPath(classpathEntry.getPath()); if (sdk != null) { IClasspathEntry[] classpathEntries = sdk.getClasspathEntries(); if (classpathEntries.length > 0) { installPath = computeInstallPath(classpathEntries[0]); } } break; case IClasspathEntry.CPE_LIBRARY: installPath = classpathEntry.getPath().removeLastSegments(1); break; case IClasspathEntry.CPE_PROJECT: case IClasspathEntry.CPE_SOURCE: installPath = computeGwtDevJarVariableValue(); if (installPath == null) { installPath = computeInstallPathFromProjectOrSourceClasspathEntry(classpathEntry); } break; default: break; } return installPath; } private IPath computeInstallPathFromProject(IJavaProject jProject) throws JavaModelException { IPath buildStagingDirectory = null; for (IClasspathEntry rawClasspath : jProject.getRawClasspath()) { if (rawClasspath.getEntryKind() == IClasspathEntry.CPE_SOURCE) { IPath sourcePathLocation = getAbsoluteLocation(rawClasspath.getPath(), jProject.getProject()); // Project could be gwt-user or gwt-dev and their source paths vary // from 2 to 3 segments - Hack. IPath outputLocation = sourcePathLocation.removeLastSegments(3) .append(GWTProjectsRuntime.STAGING_FOLDER_RELATIVE_LOCATION); if (!outputLocation.toFile().exists()) { outputLocation = sourcePathLocation.removeLastSegments(2) .append(GWTProjectsRuntime.STAGING_FOLDER_RELATIVE_LOCATION); } if (outputLocation.toFile().exists()) { buildStagingDirectory = outputLocation; break; } } } if (buildStagingDirectory != null) { return computeStagingDirectoryPath(buildStagingDirectory); } return null; } private IPath computeInstallPathFromProjectOrSourceClasspathEntry(IClasspathEntry classpathEntry) throws JavaModelException { IPath installPath; IPath entryPath = classpathEntry.getPath(); // First segment should be project name String projectName = entryPath.segment(0); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); IJavaProject jProject = JavaCore.create(project); installPath = computeInstallPathFromProject(jProject); return installPath; } private IClasspathEntry findGwtCodeServerClasspathEntry() throws JavaModelException { IClasspathEntry entry = ClasspathUtilities.findRawClasspathEntryFor(javaProject, GwtLaunchConfigurationProcessorUtilities.SUPERDEVMODE_CODESERVER_MAIN_TYPE); return entry; } private IClasspathEntry findGwtDevClasspathEntry() throws JavaModelException { IClasspathEntry gwtDev = ClasspathUtilities.findRawClasspathEntryFor(javaProject, "com.google.gwt.dev.About"); return gwtDev; } private IClasspathEntry findGwtUserClasspathEntry() throws JavaModelException { IClasspathEntry gwtUser = ClasspathUtilities.findRawClasspathEntryFor(javaProject, "com.google.gwt.core.client.GWT"); return gwtUser; } } public static final String GWT_CODESERVER_JAR = "gwt-codeserver.jar"; public static final String GWT_DEV_NO_PLATFORM_JAR = "gwt-dev.jar"; public static final String GWT_USER_JAR = "gwt-user.jar"; public static final String VALIDATION_API_JAR_PREFIX = "validation-api-"; private static final SdkFactory<GWTRuntime> factory = new SdkFactory<GWTRuntime>() { @Override public GWTRuntime newInstance(String name, IPath sdkHome) { if (isProjectBasedSdk(sdkHome)) { return new GWTProjectsRuntime(name, sdkHome); } return new GWTJarsRuntime(name, sdkHome); } }; private static final File[] NO_FILES = new File[0]; /** * Finds the {@link GWTRuntime} used by the specified project. Note that the SDK need not have * been registered. */ public static GWTRuntime findSdkFor(IJavaProject javaProject) { ExtensionQuery<GWTRuntime.IProjectBoundSdkFactory> extQuery = new ExtensionQuery<GWTRuntime.IProjectBoundSdkFactory>( GWTPlugin.PLUGIN_ID, "gwtProjectBoundSdkFactory", "class"); List<ExtensionQuery.Data<GWTRuntime.IProjectBoundSdkFactory>> sdkFactories = extQuery.getData(); for (ExtensionQuery.Data<GWTRuntime.IProjectBoundSdkFactory> sdkFactory : sdkFactories) { GWTRuntime externalGWTRuntime = sdkFactory.getExtensionPointData().newInstance(javaProject); if (externalGWTRuntime != null && externalGWTRuntime.validate().isOK()) { return externalGWTRuntime; } } ProjectBoundSdk projectBoundSdk = new ProjectBoundSdk(javaProject); if (projectBoundSdk.getInstallationPath() != null) { return projectBoundSdk; } return null; } public static SdkFactory<GWTRuntime> getFactory() { return factory; } protected static IPath computeStagingDirectoryPath(IPath rootPath) { File stagingDirectory = rootPath.toFile(); // Find the staging output directory: gwt-<platform>-<version> final File[] buildDirs = stagingDirectory.listFiles(new FileFilter() { @Override public boolean accept(File file) { return (file.isDirectory() && file.getName().startsWith("gwt-")); } }); if (buildDirs != null && buildDirs.length > 0) { return new Path(buildDirs[0].getAbsolutePath()); } return null; } /** * Returns <code>true</code> if the SDK home is a project based SDK. * * @param sdkHome * @return whether the SDK home is a project based SDK */ private static boolean isProjectBasedSdk(IPath sdkHome) { IPath location = ResourcesPlugin.getWorkspace().getRoot().getLocation(); return location.equals(sdkHome); } protected GWTRuntime(String name, IPath location) { super(name, location); } // FIXME: Why is this returning URLClassLoader instead of ClassLoader public abstract URLClassLoader createClassLoader() throws SdkException, MalformedURLException; public abstract File getDevJar() throws SdkException, JavaModelException; @Override public String getVersion() { URLClassLoader cl; final String exceptionMessage = "Cannot get version of GWT SDK \"" + getName() + "\", ensure it is configured properly"; try { cl = createClassLoader(); // Extract version from gwt-dev-<platform> Class<?> about = cl.loadClass("com.google.gwt.dev.About"); Method method = about.getMethod("getGwtVersionNum"); String versionStr = (String) method.invoke(null); return SdkUtils.cleanupVersion(versionStr); } catch (MalformedURLException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (SdkException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (ClassNotFoundException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (SecurityException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (IllegalArgumentException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (IllegalAccessException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (NoSuchMethodException e) { GWTPluginLog.logError(e, exceptionMessage); } catch (InvocationTargetException e) { GWTPluginLog.logError(e, exceptionMessage); } return ""; } /** * Returns <code>true</code> if the {@link GWTRuntime} references the gwt-dev project. */ public boolean usesGwtDevProject() { // Overridden in subclasses. return false; } @Override public abstract IStatus validate(); }