/*******************************************************************************
* 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.designer.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gdt.eclipse.designer.Activator;
import com.google.gdt.eclipse.designer.IExceptionConstants;
import com.google.gdt.eclipse.designer.common.Constants;
import com.google.gdt.eclipse.designer.model.module.GwtDocumentHandler;
import com.google.gdt.eclipse.designer.model.module.ModuleElement;
import com.google.gdt.eclipse.designer.model.module.ScriptElement;
import com.google.gdt.eclipse.designer.model.module.SetPropertyFallbackElement;
import com.google.gdt.eclipse.designer.model.module.StylesheetElement;
import com.google.gdt.eclipse.designer.model.web.WebAppElement;
import com.google.gdt.eclipse.designer.model.web.WebDocumentEditContext;
import com.google.gdt.eclipse.designer.model.web.WebUtils;
import com.google.gdt.eclipse.designer.model.web.WelcomeFileElement;
import com.google.gdt.eclipse.designer.model.web.WelcomeFileListElement;
import org.eclipse.wb.internal.core.utils.IOUtils2;
import org.eclipse.wb.internal.core.utils.Version;
import org.eclipse.wb.internal.core.utils.exception.DesignerException;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx;
import org.eclipse.wb.internal.core.utils.external.ExternalFactoriesHelper;
import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils;
import org.eclipse.wb.internal.core.utils.jdt.core.ProjectUtils;
import org.eclipse.wb.internal.core.utils.reflect.ProjectClassLoader;
import org.eclipse.wb.internal.core.utils.xml.parser.QParser;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.XmlStreamReader;
import org.apache.commons.lang.StringUtils;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.lexer.Lexer;
import org.htmlparser.lexer.Page;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.DefaultParserFeedback;
import org.htmlparser.visitors.TagFindingVisitor;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Various non UI utilities for GWT.
*
* @author scheglov_ke
* @coverage gwt.util
*/
public final class Utils {
////////////////////////////////////////////////////////////////////////////
//
// Environment
//
////////////////////////////////////////////////////////////////////////////
/**
* @return <code>true</code> if Eclipse has GPE - Google Plugin for Eclipse, installed.
*/
public static boolean hasGPE() {
if (System.getProperty("wbp.noGPE") != null) {
return false;
}
return Platform.getBundle("com.google.gwt.eclipse.core") != null;
}
////////////////////////////////////////////////////////////////////////////
//
// Libraries
//
////////////////////////////////////////////////////////////////////////////
/**
* Get absolute path to the <code>GWT_HOME</code>.
*
* @param project
* optional GWT {@link IProject}, if not <code>null</code>, then project-specific
* gwt-user.jar may be returned; if <code>null</code>, then workspace-global one.
*/
public static String getGWTLocation(IProject project) {
IPath userLibPath = getUserLibPath(project);
if (userLibPath != null) {
return userLibPath.removeLastSegments(1).toPortableString();
} else {
return null;
}
}
/**
* Get absolute path to the gwt-user.jar.
*
* @param project
* optional GWT {@link IProject}, if not <code>null</code>, then project-specific
* gwt-user.jar may be returned; if <code>null</code>, then workspace-global one.
*/
public static IPath getUserLibPath(final IProject project) {
// when no project, use workspace-global GWT_HOME
if (project == null) {
return new Path(Activator.getGWTLocation()).append("gwt-user.jar");
}
// try to find project-specific GWT location
return ExecutionUtils.runObject(new RunnableObjectEx<IPath>() {
public IPath runObject() throws Exception {
IJavaProject javaProject = JavaCore.create(project);
String[] entries = ProjectClassLoader.getClasspath(javaProject);
// try to find gwt-user.jar by name
String userJarEntry = getUserJarEntry(entries);
if (userJarEntry != null) {
return new Path(userJarEntry);
}
// try to find gwt-user.jar by contents
for (String entry : entries) {
if (entry.endsWith(".jar")) {
JarFile jarFile = new JarFile(entry);
try {
if (jarFile.getEntry("com/google/gwt/core/Core.gwt.xml") != null) {
return new Path(entry);
}
} finally {
jarFile.close();
}
}
}
// not found
return null;
}
});
}
static String getUserJarEntry(String[] entries) {
for (String entry : entries) {
// lookup for gwt-user.jar entry; it can be 'gwt-user-1.5.3.jar', so use regexp
Pattern p = Pattern.compile(".*gwt-user.*\\.jar");
Matcher m = p.matcher(entry);
if (m.matches()) {
return entry;
}
}
return null;
}
/**
* Get absolute path to the gwt-dev-windows.jar or gwt-dev-linux.jar
*
* @param project
* optional GWT {@link IProject}, if not <code>null</code>, then project-specific
* gwt-user.jar may be returned; if <code>null</code>, then workspace-global one.
*/
public static IPath getDevLibPath(IProject project) {
// try to use location of gwt-user.jar
{
String gwtLocation = getGWTLocation(project);
// Maven
if (gwtLocation.contains("/gwt/gwt-user/")) {
String gwtFolder = StringUtils.substringBefore(gwtLocation, "/gwt-user/");
String versionString = StringUtils.substringAfter(gwtLocation, "/gwt/gwt-user/");
String devFolder = gwtFolder + "/gwt-dev/" + versionString;
String devFileName = "gwt-dev-" + versionString + ".jar";
Path path = new Path(devFolder + "/" + devFileName);
if (path.toFile().exists()) {
return path;
}
}
// gwt-dev in same folder as gwt-user.jar
{
IPath path = getDevLibPath(gwtLocation);
if (path.toFile().exists()) {
return path;
}
}
}
// use gwt-dev.jar from default GWT location
{
String gwtLocation = Activator.getGWTLocation();
return getDevLibPath(gwtLocation);
}
}
private static IPath getDevLibPath(String gwtLocation) {
String devLibName = "gwt-dev.jar";
return new Path(gwtLocation).append(devLibName);
}
////////////////////////////////////////////////////////////////////////////
//
// Version
//
////////////////////////////////////////////////////////////////////////////
public static final Version GWT_2_0 = new Version(2, 0);
public static final Version GWT_2_1 = new Version(2, 1);
public static final Version GWT_2_1_1 = new Version(2, 1, 1);
public static final Version GWT_2_2 = new Version(2, 2);
public static final Version GWT_2_4 = new Version(2, 4);
public static final Version GWT_2_5 = new Version(2, 5);
/**
* @return the default version of GWT, configured in preferences.
*/
public static Version getDefaultVersion() {
String userLocation = Activator.getGWTLocation() + "/gwt-user.jar";
File userFile = new File(userLocation);
if (userFile.exists()) {
try {
JarFile jarFile = new JarFile(userFile);
try {
if (hasClassEntry(jarFile, "com.google.gwt.user.cellview.client.RowHoverEvent")) {
return GWT_2_5;
}
if (hasClassEntry(jarFile, "com.google.gwt.user.cellview.client.DataGrid")) {
return GWT_2_4;
}
if (hasClassEntry(jarFile, "com.google.gwt.canvas.client.Canvas")) {
return GWT_2_2;
}
if (hasClassEntry(jarFile, "com.google.gwt.user.client.ui.DirectionalTextHelper")) {
return GWT_2_1_1;
}
if (hasClassEntry(jarFile, "com.google.gwt.cell.client.Cell")) {
return GWT_2_1;
}
if (hasClassEntry(jarFile, "com.google.gwt.user.client.ui.LayoutPanel")) {
return GWT_2_0;
}
} finally {
jarFile.close();
}
} catch (Throwable e) {
}
}
// default version
return GWT_2_4;
}
private static boolean hasClassEntry(JarFile jarFile, String className) {
String path = className.replace('.', '/') + ".class";
return jarFile.getEntry(path) != null;
}
/**
* Returns the version of GWT using in given {@link IProject}.
*
* @param javaProject
* the GWT {@link IJavaProject}.
*/
public static Version getVersion(IProject project) {
IJavaProject javaProject = JavaCore.create(project);
return getVersion(javaProject);
}
/**
* Returns the version of GWT using in given {@link IJavaProject}.
*
* @param javaProject
* the GWT {@link IJavaProject}.
*/
public static Version getVersion(IJavaProject javaProject) {
if (ProjectUtils.hasType(javaProject, "com.google.gwt.user.cellview.client.RowHoverEvent")) {
return GWT_2_5;
}
if (ProjectUtils.hasType(javaProject, "com.google.gwt.user.cellview.client.DataGrid")) {
return GWT_2_4;
}
if (ProjectUtils.hasType(javaProject, "com.google.gwt.canvas.client.Canvas")) {
return GWT_2_2;
}
if (ProjectUtils.hasType(javaProject, "com.google.gwt.user.client.ui.DirectionalTextHelper")) {
return GWT_2_1_1;
}
if (ProjectUtils.hasType(javaProject, "com.google.gwt.cell.client.Cell")) {
return GWT_2_1;
}
if (ProjectUtils.hasType(javaProject, "com.google.gwt.user.client.ui.LayoutPanel")) {
return GWT_2_0;
}
// default
return GWT_2_4;
}
/**
* @return <code>true</code> if given version of GWT contains MVP framework.
*/
public static boolean supportMvp(Version gwtVersion) {
return gwtVersion.isHigherOrSame(GWT_2_1);
}
/**
* @return <code>true</code> if given {@link IJavaProject} support MVP framework.
*/
public static boolean supportMvp(IJavaProject javaProject) {
Version version = getVersion(javaProject);
return supportMvp(version);
}
////////////////////////////////////////////////////////////////////////////
//
// ModuleDescription utils
//
////////////////////////////////////////////////////////////////////////////
/**
* @return <code>true</code> if given module inherits required, directly or indirectly.
*/
public static boolean inheritsModule(ModuleDescription moduleDescription,
final String requiredModule) throws Exception {
final AtomicBoolean result = new AtomicBoolean();
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void endVisitModule(ModuleElement module) {
if (requiredModule.equals(module.getId())) {
result.set(true);
}
}
});
return result.get();
}
////////////////////////////////////////////////////////////////////////////
//
// Single module access
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the {@link ModuleDescription} to which belongs given {@link ICompilationUnit}.
*/
public static ModuleDescription getSingleModule(ICompilationUnit unit) throws Exception {
IPackageFragment packageFragment = (IPackageFragment) unit.getParent();
return getSingleModule(packageFragment);
}
/**
* @return the {@link ModuleDescription} to which belongs given {@link IType}.
*/
public static ModuleDescription getSingleModule(IType type) throws Exception {
IPackageFragment packageFragment = type.getPackageFragment();
return getSingleModule(packageFragment);
}
/**
* @return the {@link ModuleDescription} to which belongs given {@link IPackageFragment}.
*/
public static ModuleDescription getSingleModule(IPackageFragment pkg) throws Exception {
IResource packageResource = pkg.getUnderlyingResource();
return getSingleModule(packageResource);
}
/**
* @return the {@link ModuleDescription} to which belongs given {@link IResource}, may be
* <code>null</code> if no module found.
*/
public static ModuleDescription getSingleModule(IResource resource) throws Exception {
List<ModuleDescription> modules = getModules(resource);
// try to find marker
for (ModuleDescription module : modules) {
String content = IOUtils2.readString(module.getContents());
if (content.contains("gwtd.module.use")) {
return module;
}
}
// apply filters
for (IModuleFilter filter : getModuleFilters()) {
modules = filter.filter(modules);
}
// use first
return modules.isEmpty() ? null : modules.get(0);
}
////////////////////////////////////////////////////////////////////////////
//
// Module files searching
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the {@link ModuleDescription} that exactly corresponds to the given object, may be
* <code>null</code> there are no corresponding module or more than one module exists.
*/
public static ModuleDescription getExactModule(Object object) {
for (IModuleProvider moduleProvider : getModuleProviders()) {
ModuleDescription module = moduleProvider.getExactModule(object);
if (module != null) {
return module;
}
}
return null;
}
/**
* @return the {@link ModuleDescription} with given id, may be <code>null</code>
*/
public static ModuleDescription getModule(IJavaProject javaProject, String id) throws Exception {
for (IModuleProvider moduleProvider : getModuleProviders()) {
ModuleDescription module = moduleProvider.getModuleDescription(javaProject, id);
if (module != null) {
return module;
}
}
return null;
}
/**
* @return all {@link ModuleDescription} in given {@link IJavaProject}.
*/
public static List<ModuleDescription> getModules(IJavaProject javaProject) throws Exception {
List<ModuleDescription> modules = Lists.newArrayList();
for (IModuleProvider moduleProvider : getModuleProviders()) {
modules.addAll(moduleProvider.getModules(javaProject));
}
return modules;
}
/**
* @return the {@link ModuleDescription}s to which belongs given {@link IResource}, may be empty
* list if no module found.
*/
public static List<ModuleDescription> getModules(IResource resource) throws Exception {
List<ModuleDescription> modules = Lists.newArrayList();
for (IModuleProvider moduleProvider : getModuleProviders()) {
modules.addAll(moduleProvider.getModules(resource));
}
return modules;
}
private static List<IModuleProvider> getModuleProviders() {
return ExternalFactoriesHelper.getElementsInstances(
IModuleProvider.class,
"com.google.gdt.eclipse.designer.moduleProviders",
"provider");
}
private static List<IModuleFilter> getModuleFilters() {
return ExternalFactoriesHelper.getElementsInstances(
IModuleFilter.class,
"com.google.gdt.eclipse.designer.moduleProviders",
"filter");
}
////////////////////////////////////////////////////////////////////////////
//
// Module packages/folders access
//
////////////////////////////////////////////////////////////////////////////
/**
* Accepts some {@link IPackageFragment} in "source" package and returns root "source" package.
*/
public static IPackageFragment getRootSourcePackage(IPackageFragment pkg) throws Exception {
IPackageFragmentRoot sourceRoot = (IPackageFragmentRoot) pkg.getParent();
IPackageFragment clientRoot = pkg;
while (isModuleSourcePackage(pkg)) {
clientRoot = pkg;
String pkgName = pkg.getElementName();
String pkgParentName = CodeUtils.getPackage(pkgName);
pkg = sourceRoot.getPackageFragment(pkgParentName);
}
return clientRoot;
}
/**
* @return <code>true</code> if given {@link IPackageFragment} is "source" package of some GWT
* module.
*/
public static boolean isModuleSourcePackage(IPackageFragment packageFragment) throws Exception {
final String packageName = packageFragment.getElementName();
// check enclosing module
ModuleDescription module = getSingleModule(packageFragment);
if (module != null) {
final AtomicBoolean result = new AtomicBoolean();
ModuleVisitor.accept(module, new ModuleVisitor() {
@Override
public boolean visitModule(ModuleElement moduleElement) {
String modulePackage = CodeUtils.getPackage(moduleElement.getId()) + ".";
if (packageName.startsWith(modulePackage)) {
String folderInModule = packageName.substring(modulePackage.length()).replace('.', '/');
if (moduleElement.isInSourceFolder(folderInModule)) {
result.set(true);
return false;
}
}
return true;
}
});
return result.get();
}
// no enclosing module
return false;
}
////////////////////////////////////////////////////////////////////////////
//
// Module reading
//
////////////////////////////////////////////////////////////////////////////
private static final Map<String, ModuleElement> m_readModule = Maps.newTreeMap();
/**
* Reads module definition from given {@link ModuleDescription}.
*/
public static ModuleElement readModule(ModuleDescription moduleDescription) throws Exception {
String id = moduleDescription.getId();
InputStream contents = moduleDescription.getContents();
return readModule(id, contents);
}
/**
* Reads module definition from given stream.
*/
public static ModuleElement readModule(String id, InputStream inputStream) throws Exception {
// read content, with correct encoding
String contents;
{
Reader reader = new XmlStreamReader(inputStream);
contents = IOUtils2.readString(reader);
}
// check cache
String key = id + "|" + contents;
{
ModuleElement moduleElement = m_readModule.get(key);
if (moduleElement != null) {
return moduleElement;
}
}
// parse using GWTDocumentHandler
GwtDocumentHandler documentHandler = new GwtDocumentHandler();
try {
QParser.parse(new StringReader(contents), documentHandler);
} catch (Throwable e) {
throw new DesignerException(IExceptionConstants.INVALID_MODULE_FILE, id);
}
// prepare module element
ModuleElement moduleElement = documentHandler.getModuleElement();
moduleElement.setId(id);
moduleElement.finalizeLoading();
// fill cache
m_readModule.put(key, moduleElement);
// done
return moduleElement;
}
////////////////////////////////////////////////////////////////////////////
//
// Resources
//
////////////////////////////////////////////////////////////////////////////
/**
* @param moduleFile
* the module *.gwt.xml file.
* @param resource
* the path of "public" resource.
*
* @return the existing resource {@link IFile}, may be <code>null</code>.
*/
public static IFile getFileForResource(IFile moduleFile, String resource) throws Exception {
List<IFile> files = getFilesForResources(moduleFile, ImmutableList.of(resource));
return !files.isEmpty() ? files.get(0) : null;
}
/**
* Returns the {@link IFile}'s for "public" resources in given module. An {@link IFile} for
* resource is returned only if this resource located in some IProject in workspace, not just in
* jar.
*
* @param moduleFile
* the module *.gwt.xml file.
* @param resources
* the paths of "public" resources, relative to the one of the "public" package of some
* module.
*/
public static List<IFile> getFilesForResources(IFile moduleFile, Collection<String> resources)
throws Exception {
// prepare IContainer's for each "src" folder
List<IContainer> sourceFolders;
{
IJavaProject javaProject = JavaCore.create(moduleFile.getProject());
sourceFolders = CodeUtils.getSourceContainers(javaProject, true);
}
// fill list of IFile's
List<IFile> files = Lists.newArrayList();
for (String resourcePath : resources) {
IFile file = getFileForResource(moduleFile, sourceFolders, resourcePath);
if (file != null) {
files.add(file);
}
}
// return IFile's
return files;
}
private static IFile getFileForResource(IFile moduleFile,
final List<IContainer> sourceFolders,
final String resourcePath) throws Exception {
final IFile files[] = new IFile[1];
// check public resources
ModuleVisitor.accept(new DefaultModuleDescription(moduleFile), new ModuleVisitor() {
@Override
public void visitPublicPackage(ModuleElement module, String packageName) throws Exception {
for (IContainer sourceContainer : sourceFolders) {
checkContainer(packageName, sourceContainer);
}
}
private void checkContainer(String packageName, IContainer sourceContainer) {
if (files[0] == null) {
String filePath = packageName.replace('.', '/') + "/" + resourcePath;
IFile file = sourceContainer.getFile(new Path(filePath));
if (file.exists()) {
files[0] = file;
}
}
}
});
// check "web" folder
if (files[0] == null) {
files[0] = getFileForResource_webFolder(moduleFile, resourcePath);
}
// done
return files[0];
}
/**
* @return the existing resource in "web" folder, may be <code>null</code>.
*/
private static IFile getFileForResource_webFolder(IFile moduleFile, String pathInWebFolder) {
IProject project = moduleFile.getProject();
String webFolderName = WebUtils.getWebFolderName(project);
IFolder webFolder = project.getFolder(webFolderName);
if (webFolder != null && webFolder.exists()) {
IFile file = webFolder.getFile(new Path(pathInWebFolder));
if (file.exists()) {
return file;
}
}
return null;
}
/**
* @return <code>true</code> if resource with given path exists in module.
*/
public static boolean isExistingResource(ModuleDescription moduleDescription, String path)
throws Exception {
InputStream stream = getResource(moduleDescription, path);
IOUtils.closeQuietly(stream);
return stream != null;
}
/**
* 1. GWT 1.5 under 1.5. Resource path is path in "public", for example just "home.gif".
* <p>
* 2. GWT 1.5 under 1.6. In GWT 1.6 all resources should be in "web", including resources from
* "public", which copied into sub-folder with "logical" module name (fully qualified name or
* value of "rename-to"). So, to access resource from "web" itself, use just "home.gif". To access
* resource from "my.long.module.Name" with "rename-to=module", use "module/home.gif". Note, that
* copying "public" resources happens only when you run hosted mode, but not when you just use
* design time, i.e. it should be considered as "virtual" copying. So, we should first check
* "public" folders and if not found, check "web".
* <p>
* 3. GWT 1.6 under 1.6.
*/
public static InputStream getResource(ModuleDescription moduleDescription, String path)
throws Exception {
// try "public" module resource
{
InputStream result = getResource_modulePublic(moduleDescription, path);
if (result != null) {
return result;
}
}
// check "web" folder
IProject project = moduleDescription.getProject();
String webFolderName = WebUtils.getWebFolderName(project);
IPath webFolderPath = new Path(webFolderName);
IFolder webFolder = project.getFolder(webFolderPath);
if (webFolder != null) {
IFile file = webFolder.getFile(new Path(path));
if (file.exists()) {
InputStream result = file.getContents(true);
// If CSS, then return UTF-8 bytes.
if (result != null && path.toUpperCase().endsWith(".CSS")) {
String stringContent = new String(IOUtils2.readBytes(result), file.getCharset());
result = new ByteArrayInputStream(stringContent.getBytes("UTF-8"));
}
return result;
}
}
// no resource
return null;
}
/**
* If given path has module name as prefix, then resource may be in "public" folder.
*/
private static InputStream getResource_modulePublic(ModuleDescription moduleDescription,
String path) throws Exception {
// prepare "public" path
final String publicResourcePath;
{
ModuleElement module = readModule(moduleDescription);
String prefix = module.getName() + "/";
if (path.startsWith(prefix)) {
publicResourcePath = path.substring(prefix.length());
} else {
publicResourcePath = path;
}
}
// check "public" folders
final InputStream[] result = new InputStream[1];
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void visitPublicPackage(ModuleElement module, String packageName) throws Exception {
if (result[0] == null) {
String fullResourcePath = packageName.replace('.', '/') + "/" + publicResourcePath;
result[0] = getResourcesProvider().getResourceAsStream(fullResourcePath);
}
}
});
return result[0];
}
/*private static InputStream getResource_modulePublic(IFile moduleFile, String path) throws Exception {
ModuleElement module = readModule(moduleFile);
String prefix = module.getName() + "/";
if (path.startsWith(prefix)) {
final String publicResourcePath = path.substring(prefix.length());
final InputStream[] result = new InputStream[1];
ModuleVisitor.accept(moduleFile, new ModuleVisitor() {
@Override
public void visitPublicPackage(ModuleElement module, String packageName) throws Exception {
if (result[0] == null) {
String fullResourcePath = packageName.replace('.', '/') + "/" + publicResourcePath;
result[0] = getResourcesProvider().getResourceAsStream(fullResourcePath);
}
}
});
return result[0];
}
// not module "public" path
return null;
}*/
/**
* @return the default name of HTML file for given module name.
*/
public static String getDefaultHTMLName(String moduleId) {
return StringUtils.substringAfterLast(moduleId, ".") + ".html";
}
////////////////////////////////////////////////////////////////////////////
//
// *.css/*.js resources
//
////////////////////////////////////////////////////////////////////////////
/**
* @return HTML of the first '!doctype' tag (if any), otherwise returns empty string. Note: return
* <code>null</code> if '!doctype' value couldn't be determined.
*/
public static String getDocType(ModuleDescription moduleFile) throws Exception {
IFile htmlFile = getHTMLFile(moduleFile);
if (htmlFile != null) {
List<TagNode> tags = getHTMLFileTags(htmlFile, "!doctype");
for (TagNode tag : tags) {
return tag.toHtml();
}
return "";
}
return null;
}
/**
* Gets "public" paths of the CSS resources referenced by module. There are two sources for CSS
* resources:
* <ol>
* <li><code>link</code> tags in HTML file;</li>
* <li><code>stylesheet</code> tags in module file.</li>
* </ol>
* <p>
* It expects that unit is inside of some GWT module with single module file. It also expects that
* default HTML file is used, see {@link #getHTMLFile(IFile)}.
*/
public static List<String> getCssResources(ModuleDescription moduleDescription) throws Exception {
final List<String> cssResources = Lists.newArrayList();
// add CSS resources from module file, *.gwt.xml
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void endVisitModule(ModuleElement module) {
for (StylesheetElement stylesheetElement : module.getStylesheetElements()) {
String src = stylesheetElement.getSrc();
if (src.startsWith("/")) {
src = src.substring(1);
}
if (src.startsWith("../")) {
src = src.substring(3);
}
cssResources.add(src);
}
}
});
// add CSS resources from HTML
{
IFile htmlFile = getHTMLFile(moduleDescription);
if (htmlFile != null) {
String resourcePrefix = getHTMLResourcePrefix(htmlFile);
List<TagNode> tags = getHTMLFileTags(htmlFile, "link");
for (TagNode tag : tags) {
if ("stylesheet".equals(tag.getAttribute("rel"))) {
String href = tag.getAttribute("href");
String resourcePath = resourcePrefix + href;
cssResources.add(resourcePath);
}
}
}
}
// OK, we have all CSS resources for module
return cssResources;
}
/**
* Resources referenced from HTML file are located relative to folder of HTML file.
*/
private static String getHTMLResourcePrefix(IFile htmlFile) throws CoreException {
String htmlPath = (String) htmlFile.getSessionProperty(KEY_RESOURCE_PATH);
int index = htmlPath.indexOf("/");
return index == -1 ? "" : htmlPath.substring(0, index + 1);
}
/**
* Gets "public" paths of the CSS resources referenced by module. There are two sources for CSS
* resources:
* <ol>
* <li><code>script</code> tags in HTML file;</li>
* <li><code>script</code> tags in module file.</li>
* </ol>
* <p>
* It expects that unit is inside of some GWT module with single module file. It also expects that
* default HTML file is used, see {@link #getHTMLFile(IFile)}.
*/
public static List<String> getScriptResources(ModuleDescription moduleDescription)
throws Exception {
final List<String> scriptResources = Lists.newArrayList();
// add <script> resources from HTML
{
IFile htmlFile = getHTMLFile(moduleDescription);
if (htmlFile != null) {
List<TagNode> tags = getHTMLFileTags(htmlFile, "script");
for (TagNode tag : tags) {
String src = tag.getAttribute("src");
if (src != null) {
scriptResources.add(src);
}
}
}
}
// add CSS resources from module file, *.gwt.xml
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void endVisitModule(ModuleElement module) {
for (ScriptElement scriptElement : module.getScriptElements()) {
String src = scriptElement.getSrc();
if (src != null) {
//scriptResources.add(mainModuleName + "/" + src);
scriptResources.add(src);
}
}
}
});
// exclude some scripts
for (Iterator<String> I = scriptResources.iterator(); I.hasNext();) {
String src = I.next();
// GWT 1.4+ system script
if (src.indexOf(".nocache.js") != -1) {
I.remove();
}
// Google Maps
if (src.indexOf("maps.google.com/maps?") != -1) {
I.remove();
}
}
// OK, we have all CSS resources for module
return scriptResources;
}
private static final QualifiedName KEY_RESOURCE_PATH =
new QualifiedName("com.google.gdt.eclipse.designer", "resourcePath");
/**
* @return the {@link IFile} for module HTML file, may be <code>null</code>.
*/
public static IFile getHTMLFile(ModuleDescription moduleDescription) throws Exception {
// IFile can only be returned if this ModuleDescription is a DefaultModuleDescription,
// aka, if it wraps an IFile itself
IFile moduleFile;
if (moduleDescription instanceof DefaultModuleDescription) {
moduleFile = ((DefaultModuleDescription) moduleDescription).getFile();
} else {
return null;
}
// try welcome-file
{
IFile file = getHTMLFile_web(moduleFile);
if (file != null) {
return file;
}
}
// try default HTML
String moduleId = moduleDescription.getId();
String htmlFileName = getDefaultHTMLName(moduleId);
IFile file = getFileForResource(moduleFile, htmlFileName);
if (file != null) {
file.setSessionProperty(KEY_RESOURCE_PATH, htmlFileName);
}
return file;
}
/**
* Tries to find HTML file specified in <code>web.xml</code>, as "welcome-file".
*
* @return the {@link IFile} for HTML file, may be <code>null</code>.
*/
private static IFile getHTMLFile_web(IFile moduleFile) throws Exception {
IFile webFile = getFileForResource_webFolder(moduleFile, "WEB-INF/web.xml");
if (webFile == null) {
return null;
}
// ignore if empty
String webFileContent = IOUtils2.readString(webFile);
if (StringUtils.isBlank(webFileContent)) {
return null;
}
// search for "welcome-file"
List<String> welcomeFileNames = Lists.newArrayList();
try {
WebDocumentEditContext context = new WebDocumentEditContext(webFile);
try {
WebAppElement webApp = context.getWebAppElement();
for (WelcomeFileListElement welcomeFileList : webApp.getWelcomeFileListElements()) {
for (WelcomeFileElement welcomeFile : welcomeFileList.getWelcomeFiles()) {
String welcomeFileName = welcomeFile.getName();
welcomeFileNames.add(welcomeFileName);
}
}
} finally {
context.disconnect();
}
} catch (Throwable e) {
throw new DesignerException(IExceptionConstants.INVALID_WEB_XML,
e,
webFile.getFullPath().toPortableString(),
webFileContent);
}
// try to find resource for "welcome-file"
for (String welcomeFileName : welcomeFileNames) {
IFile file = getFileForResource(moduleFile, welcomeFileName);
if (file != null) {
file.setSessionProperty(KEY_RESOURCE_PATH, welcomeFileName);
return file;
}
}
// not found
return null;
}
/**
* Uses "htmlParser" library to parse given HTML and extract tags with given name.
*
* @return the {@link List} of found {@link TagNode}'s.
*/
private static List<TagNode> getHTMLFileTags(IFile htmlFile, String tagName) throws Exception {
// find nodes
Node[] tags;
{
String htmlContents = IOUtils2.readString(htmlFile);
Lexer lexer = new Lexer(new Page(htmlContents));
Parser parser = new Parser(lexer, new DefaultParserFeedback(DefaultParserFeedback.QUIET));
TagFindingVisitor visitor = new TagFindingVisitor(new String[]{tagName});
parser.visitAllNodesWith(visitor);
tags = visitor.getTags(0);
}
// convert into List<TagNode>
List<TagNode> tagNodes = Lists.newArrayList();
CollectionUtils.addAll(tagNodes, tags);
return tagNodes;
}
////////////////////////////////////////////////////////////////////////////
//
// Locale
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the default locale from "set-property-fallback" property in module file.
*/
public static String getDefaultLocale(ModuleDescription moduleDescription) throws Exception {
final AtomicReference<String> defaultLocale = new AtomicReference<String>("default");
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void endVisitModule(ModuleElement module) {
List<SetPropertyFallbackElement> elements = module.getSetPropertyFallbackElements();
for (SetPropertyFallbackElement element : elements) {
if (element.getName().equals("locale")) {
defaultLocale.set(element.getValue());
}
}
}
});
return defaultLocale.get();
}
////////////////////////////////////////////////////////////////////////////
//
// RemoteService interface/impl
//
////////////////////////////////////////////////////////////////////////////
/**
* Checks that given resource is Java file with remote service.
*/
public static boolean isRemoteService(IResource resource) throws CoreException {
if (resource != null && resource instanceof IFile && resource.getName().endsWith(".java")) {
ICompilationUnit cu = (ICompilationUnit) JavaCore.create(resource);
return isRemoteService(cu);
}
return false;
}
/**
* Checks that given Java element is part of unit that has RemoteService.
*/
public static boolean isRemoteService(IJavaElement element) throws CoreException {
IType type = CodeUtils.getType(element);
if (type != null && type.exists() && type.isInterface()) {
return CodeUtils.isSuccessorOf(type, Constants.CLASS_REMOTE_SERVICE);
}
return false;
}
/**
* Checks that given Java element is part of unit that has RemoteService.
*/
public static boolean isRemoteServiceImpl(IJavaElement element) throws Exception {
IType type = CodeUtils.getType(element);
if (type != null && type.exists()) {
return CodeUtils.isSuccessorOf(type, Constants.CLASS_REMOTE_SERVICE_IMPL);
}
return false;
}
////////////////////////////////////////////////////////////////////////////
//
// EntryPoint
//
////////////////////////////////////////////////////////////////////////////
/**
* Checks that given Java element is part part of unit that has EntryPoint.
*/
public static boolean isEntryPoint(IJavaElement element) throws Exception {
IType type = CodeUtils.getType(element);
if (type != null && type.exists()) {
return CodeUtils.isSuccessorOf(type, Constants.CLASS_ENTRY_POINT);
}
return false;
}
////////////////////////////////////////////////////////////////////////////
//
// Progress
//
////////////////////////////////////////////////////////////////////////////
public static final NullProgressMonitor NULL_PROGRESS_MONITOR = new NullProgressMonitor();
/**
* @return the given {@link IProgressMonitor} is it is not <code>null</code> or shared
* {@link NullProgressMonitor} instance.
*/
public static IProgressMonitor getNonNullMonitor(IProgressMonitor monitor) {
if (monitor == null) {
monitor = NULL_PROGRESS_MONITOR;
}
return monitor;
}
////////////////////////////////////////////////////////////////////////////
//
// Projects
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the {@link IJavaProject} with given name.
*/
public static IJavaProject getJavaProject(String name) {
IProject project = getProject(name);
return JavaCore.create(project);
}
/**
* @return the {@link IProject} with given name.
*/
public static IProject getProject(String name) {
return ResourcesPlugin.getWorkspace().getRoot().getProject(name);
}
/**
* @return <code>true</code> if given project is GWT project.
*/
public static boolean isGWTProject(final IJavaProject javaProject) {
return ExecutionUtils.runObjectIgnore(new RunnableObjectEx<Boolean>() {
public Boolean runObject() throws Exception {
return ProjectUtils.hasType(javaProject, "com.google.gwt.user.client.ui.Widget");
}
}, false);
}
/**
* @return <code>true</code> if given project is GWT project.
*/
public static boolean isGWTProject(final IProject project) {
return ExecutionUtils.runObjectIgnore(new RunnableObjectEx<Boolean>() {
public Boolean runObject() throws Exception {
IJavaProject javaProject = JavaCore.create(project);
return isGWTProject(javaProject);
}
}, false);
}
/**
* @return <code>true</code> if given project is GPE GWT project.
*/
public static boolean isGpeGwtProject(IProject project) {
return ProjectUtils.hasNature(project, Constants.GPE_NATURE_ID) && isGWTProject(project);
}
/**
* Convenience method to get access to the {@link IJavaModel}.
*/
public static IJavaModel getJavaModel() {
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
return JavaCore.create(workspaceRoot);
}
/**
* @return the GWT Java projects.
*/
public static List<IJavaProject> getGWTProjects() throws CoreException {
List<IJavaProject> gwtProjects = Lists.newArrayList();
for (IJavaProject javaProject : getJavaModel().getJavaProjects()) {
if (isGWTProject(javaProject.getProject())) {
gwtProjects.add(javaProject);
}
}
return gwtProjects;
}
////////////////////////////////////////////////////////////////////////////
//
// AST
//
////////////////////////////////////////////////////////////////////////////
/**
* Parse given model {@link ICompilationUnit} into AST {@link CompilationUnit}.
*/
public static CompilationUnit parseUnit(ICompilationUnit compilationUnit) {
ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setSource(compilationUnit);
parser.setResolveBindings(true);
return (CompilationUnit) parser.createAST(null);
}
}