/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, Inc.
* 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
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.editor.groovy.types;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.eclipse.core.compiler.GroovySnippetCompiler;
import org.codehaus.groovy.eclipse.core.model.GroovyProjectFacade;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.groovy.search.VariableScope;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.grails.ide.eclipse.core.GrailsCoreActivator;
import org.grails.ide.eclipse.core.internal.plugins.GrailsCore;
import org.grails.ide.eclipse.core.internal.plugins.GrailsElementKind;
import org.grails.ide.eclipse.core.internal.plugins.IGrailsProjectInfo;
/**
* Caches {@link ClassNode}s that are generally needed by the project.
* The cache is refreshed whenever the classpath changes
* @author Andrew Eisenberg
* @author Nieraj Singh
* @created Jan 28, 2010
*/
public class PerProjectTypeCache implements IGrailsProjectInfo {
private final Map<String, ClassNode> classNodeCache = new HashMap<String, ClassNode>();
private IProject project;
private GroovySnippetCompiler snippetCompiler;
public void dispose() {
synchronized (GrailsCore.get().getLockForProject(project)) {
project = null;
snippetCompiler.cleanup();
snippetCompiler = null;
classNodeCache.clear();
}
}
public IProject getProject() {
return project;
}
/**
* The cache is flushed when the classpath changes or there is a refresh dependencies
* Cache is selectively flushed when a service class changes
*/
public void projectChanged(GrailsElementKind[] changeKinds,
IResourceDelta change) {
synchronized (GrailsCore.get().getLockForProject(project)) {
boolean foundRelevantChange = changeKinds.length>0;
//TODO: might be more precise than the code above, but the commented code below is buggy:
// it misses the creation of new types and packages and that
// leads to problems with those types and packages not being resolvable
// from some of the grails support infrastucture because of cache staleness.
// for (GrailsElementKind changeKind : changeKinds) {
// if (changeKind == GrailsElementKind.PROJECT ||
// changeKind == GrailsElementKind.CLASSPATH) {
// foundRelevantChange = true;
// break;
// }
// }
if (foundRelevantChange) {
classNodeCache.clear();
// force resetting of the snippet compiler
setProject(project);
}
}
}
public void clearFromCache(String fullyQualifiedName) {
// classNodeCache.remove(fullyQualifiedName);
classNodeCache.clear();
// force resetting of the snippet compiler
setProject(project);
}
public void setProject(IProject project) {
this.project = project;
GroovyProjectFacade groovyProject = new GroovyProjectFacade(JavaCore.create(project));
if (snippetCompiler != null) {
snippetCompiler.cleanup();
}
snippetCompiler = new GroovySnippetCompiler(groovyProject);
}
/**
* Returns a ClassNode for the given qualified name.
* If the class is not found, then Object is returned.
* If class is found, then cache the class and return it
*
* @param qualifiedName
* @return a {@link ClassNode} for the given qualified name, or {@link Object}
* if not found.
*/
public ClassNode getClassNode(String qualifiedName) {
synchronized (GrailsCore.get().getLockForProject(project)) {
ClassNode node = classNodeCache.get(qualifiedName);
if (node == null) {
node = createClassNode(qualifiedName);
if (node != null) {
classNodeCache.put(qualifiedName, node);
} else {
// will be null if cache is not initialized
// or if class can't be found.
node = VariableScope.OBJECT_CLASS_NODE;
}
}
return node;
}
}
/**
* Attempt to find a reference to this qualified name in the project
* Does not cache
* @param qualifiedName the fully qualified name of the type to find
* @return a {@link ClassNode} for this qualified name, or null
* if not found.
*/
public ClassNode createClassNode(String qualifiedName) {
if (snippetCompiler == null) {
// not initialized
return null;
}
// FIXADE I don't *think* we need this any more since we are using a
// SearchableEnvironment for the snippet compiler.
// Job[] jobs = Job.getJobManager().find(ResourcesPlugin.FAMILY_AUTO_BUILD);
// for (Job job : jobs) {
// int i = 0;
// while (job.getState() == Job.RUNNING && i++ < 8) {
// System.out.println("Build job running...");
// try {
// synchronized (this) {
// wait(1000);
// }
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
try {
ModuleNode module = snippetCompiler.compile(qualifiedName);
BlockStatement statements = module.getStatementBlock();
if (statements != null && statements.getStatements() != null && statements.getStatements().size() == 1) {
Statement s = statements.getStatements().get(0);
Expression expr;
if (s instanceof ReturnStatement) {
expr = ((ReturnStatement) s).getExpression();
} else if (s instanceof ExpressionStatement) {
expr = ((ExpressionStatement) s).getExpression();
} else {
expr = null;
}
if (expr instanceof ClassExpression) {
ClassNode node = ((ClassExpression) expr).getType();
return node;
}
}
} catch (Exception e) {
GrailsCoreActivator.log("Exception compiling snippet " + qualifiedName, e); //$NON-NLS-1$
}
return null;
}
/**
* Creates a groovy ClassNode from the source code passed in by the compilation unit.
* Assumes that the desired class name matches the name of the compilation unit.
* Assumes this is a groovy file.
* @param unit create a {@link ClassNode} for this compilation unit
* @return the parsed class node or null if not found or there is a parsing error
*/
public ClassNode createClassNodeFromSource(ICompilationUnit unit) {
if (snippetCompiler == null) {
// not initialized
return null;
}
String contents = String.valueOf(((CompilationUnit) unit).getContents());
ModuleNode module = snippetCompiler.compile(contents);
String primaryClassName = unit.getElementName();
int nameEnd = primaryClassName.indexOf('.');
primaryClassName = primaryClassName.substring(0, nameEnd);
for (ClassNode clazz : module.getClasses()) {
if (primaryClassName.equals(clazz.getNameWithoutPackage())) {
return clazz;
}
}
return module.getScriptClassDummy();
}
}