/*******************************************************************************
* 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.builders.participant;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gdt.eclipse.designer.Activator;
import com.google.gdt.eclipse.designer.common.Constants;
import com.google.gdt.eclipse.designer.model.module.ModuleElement;
import com.google.gdt.eclipse.designer.util.ModuleDescription;
import com.google.gdt.eclipse.designer.util.ModuleVisitor;
import com.google.gdt.eclipse.designer.util.Utils;
import com.google.gdt.eclipse.designer.util.resources.IResourcesProvider;
import org.eclipse.wb.internal.core.utils.IOUtils2;
import org.eclipse.wb.internal.core.utils.ast.AstNodeUtils;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.jdt.core.CodeUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
/**
* Compilation participant that check that all imported packages are "source" packages of some
* inherited module.
*
* @author scheglov_ke
* @coverage gwt.compilation.participant
*/
public final class MyCompilationParticipant extends AbstractCompilationParticipant {
public static boolean ENABLED = true;
/**
* If the <code>com.google.gdt.eclipse.designer.wizards</code> plug-in is present, then it
* indicates that the user has the pre-GPE integrated version of GWT Designer.
*/
public static boolean WIZARD_PLUGIN_PRESENT =
Platform.getBundle("com.google.gdt.eclipse.designer.wizards") != null;
public static final String MARKER_ID = "com.google.gdt.eclipse.designer.problem";
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public MyCompilationParticipant() {
super(MARKER_ID);
}
////////////////////////////////////////////////////////////////////////////
//
// Active check
//
////////////////////////////////////////////////////////////////////////////
@Override
public boolean isActive(IJavaProject project) {
return ENABLED && WIZARD_PLUGIN_PRESENT && Utils.isGWTProject(project);
}
@Override
public boolean isAnnotationProcessor() {
return true;
}
////////////////////////////////////////////////////////////////////////////
//
// Compiling
//
////////////////////////////////////////////////////////////////////////////
@Override
protected void addMarkers(List<MarkerInfo> newMarkers,
IFile file,
ICompilationUnit modelUnit,
CompilationUnit astUnit) throws Exception {
// look if checking is enabled
{
IPreferenceStore preferenceStore = Activator.getDefault().getPreferenceStore();
if (!preferenceStore.getBoolean(Constants.P_BUILDER_CHECK_CLIENT_CLASSPATH)) {
return;
}
}
// check if unit is in source package
if (!Utils.isModuleSourcePackage((IPackageFragment) modelUnit.getParent())) {
return;
}
//
{
ModuleDescription moduleDescription = Utils.getSingleModule(file);
if (moduleDescription != null) {
// prepare document
IDocument document;
{
String contents = IOUtils2.readString(file);
document = new Document(contents);
}
// add error markers for not imported types
IResourcesProvider resourcesProvider = moduleDescription.getResourcesProvider();
try {
addMarkers_notImportedTypes(
newMarkers,
resourcesProvider,
moduleDescription,
astUnit,
file,
document);
} finally {
resourcesProvider.dispose();
}
}
}
}
/**
* Adds error markers for types that are not visible in inherited "source" packages.
*/
private void addMarkers_notImportedTypes(final List<MarkerInfo> newMarkers,
final IResourcesProvider resourcesProvider,
ModuleDescription moduleDescription,
CompilationUnit astUnit,
final IFile file,
final IDocument document) throws Exception {
final IJavaProject javaProject = JavaCore.create(file.getProject());
// prepare list of source packages
final List<SourcePackageDescriptor> sourcePackages = Lists.newArrayList();
ModuleVisitor.accept(moduleDescription, new ModuleVisitor() {
@Override
public void visitSourcePackage(ModuleElement module, String packageName, boolean superSource)
throws Exception {
sourcePackages.add(new SourcePackageDescriptor(packageName, superSource));
}
});
// validate all types in CompilationUnit
astUnit.accept(new ASTVisitor() {
private final Set<String> m_validClasses = Sets.newTreeSet();
private final Set<String> m_invalidClasses = Sets.newTreeSet();
@Override
public boolean visit(SingleMemberAnnotation node) {
return false;
}
@Override
public boolean visit(NormalAnnotation node) {
return false;
}
@Override
public void postVisit(final ASTNode node) {
ExecutionUtils.runIgnore(new RunnableEx() {
public void run() throws Exception {
postVisitEx(node);
}
});
}
private void postVisitEx(ASTNode node) throws Exception {
// ignore imports
if (AstNodeUtils.getEnclosingNode(node, ImportDeclaration.class) != null) {
return;
}
// check known cases
if (node instanceof SimpleType) {
SimpleType simpleType = (SimpleType) node;
ITypeBinding typeBinding = simpleType.resolveBinding();
checkNode(node, typeBinding);
} else if (node instanceof SimpleName) {
SimpleName simpleName = (SimpleName) node;
if (simpleName.resolveBinding().getKind() == IBinding.TYPE
&& node.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY) {
ITypeBinding typeBinding = simpleName.resolveTypeBinding();
checkNode(node, typeBinding);
}
}
}
private void checkNode(ASTNode node, ITypeBinding typeBinding) throws Exception {
if (typeBinding != null) {
// ignore generics type variable
if (typeBinding.isTypeVariable()) {
return;
}
// only top level types can be found as source
while (typeBinding.getDeclaringClass() != null) {
typeBinding = typeBinding.getDeclaringClass();
}
// check this type
String typeName = AstNodeUtils.getFullyQualifiedName(typeBinding, true);
if (isSecondarySourceType(typeName)) {
return;
}
checkClass(node, typeName);
}
}
private boolean isSecondarySourceType(String typeName) throws Exception {
// usually secondary type can not be found using this way
IType type = javaProject.findType(typeName);
if (type == null) {
return true;
}
// "secondary source type" has compilation unit
ICompilationUnit compilationUnit = type.getCompilationUnit();
if (compilationUnit == null) {
return false;
}
// check if type name in same as unit name
String unitName = compilationUnit.getElementName();
unitName = StringUtils.removeEnd(unitName, ".java");
return !typeName.endsWith("." + unitName);
}
/**
* Check that class with given name is defined in this or inherited module.
*/
private void checkClass(ASTNode node, String className) throws Exception {
if (!isValid(className)) {
markAsInvalid(node, className);
}
}
/**
* @return <code>true</code> if given class is valid.
*/
private boolean isValid(String className) {
// check cached valid classes
if (m_validClasses.contains(className)) {
return true;
}
// check cached invalid classes
if (m_invalidClasses.contains(className)) {
return false;
}
// no information in caches, do checks
for (SourcePackageDescriptor sourcePackageDescriptor : sourcePackages) {
if (sourcePackageDescriptor.isValidClass(resourcesProvider, className)) {
m_validClasses.add(className);
return true;
}
}
// mark as invalid
m_invalidClasses.add(className);
return false;
}
private void markAsInvalid(ASTNode node, String className) throws Exception {
String message =
className
+ " can not be found in source packages. "
+ "Check the inheritance chain from your module; "
+ "it may not be inheriting a required module or a module "
+ "may not be adding its source path entries properly.";
String moduleNameToImport = getEnclosingModule(resourcesProvider, className);
newMarkers.add(createMarkerInfo_importModule(
file,
document,
node.getStartPosition(),
node.getLength(),
message,
moduleNameToImport));
}
});
}
/**
* @return the name of GWT module that contains given class, may be <code>null</code>.
*/
private static String getEnclosingModule(IResourcesProvider resourcesProvider, String className)
throws Exception {
String packageName = CodeUtils.getPackage(className);
while (packageName.length() != 0) {
List<String> files = resourcesProvider.listFiles(packageName.replace('.', '/'));
for (String file : files) {
if (file.indexOf('/') == -1 && file.endsWith(".gwt.xml")) {
String shortModuleName = StringUtils.substring(file, 0, -".gwt.xml".length());
return packageName + "." + shortModuleName;
}
}
// go up
packageName = CodeUtils.getPackage(packageName);
}
return null;
}
private static MarkerInfo createMarkerInfo_importModule(IResource resource,
IDocument document,
int start,
int length,
String message,
String moduleNameToImport) throws BadLocationException {
int line = document.getLineOfOffset(start);
return new MarkerInfoImportModule(resource,
start,
start + length,
line,
IMarker.SEVERITY_ERROR,
message,
moduleNameToImport);
}
////////////////////////////////////////////////////////////////////////////
//
// Source package
//
////////////////////////////////////////////////////////////////////////////
/**
* Descriptor for source package.
*/
private static final class SourcePackageDescriptor {
private final String m_packageName;
private final boolean m_superSource;
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public SourcePackageDescriptor(String packageName, boolean superSource) {
m_packageName = packageName;
m_superSource = superSource;
}
////////////////////////////////////////////////////////////////////////////
//
// Access
//
////////////////////////////////////////////////////////////////////////////
/**
* Checks if class with given name exists in this package.
*/
public boolean isValidClass(IResourcesProvider resourcesProvider, String className) {
// prepare path to the source of class with given name
String sourceFilePath;
if (m_superSource) {
sourceFilePath = (m_packageName + "." + className).replace('.', '/') + ".java";
} else {
if (!className.startsWith(m_packageName)) {
return false;
}
sourceFilePath = className.replace('.', '/') + ".java";
}
// check that we can find source for class with given name
InputStream resourceAsStream = null;
try {
resourceAsStream = resourcesProvider.getResourceAsStream(sourceFilePath);
return resourceAsStream != null;
} catch (Throwable e) {
return false;
} finally {
IOUtils.closeQuietly(resourceAsStream);
}
}
}
}