/*******************************************************************************
* 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.Collections;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.groovy.ast.ClassNode;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.jdt.groovy.search.VariableScope;
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;
import org.grails.ide.eclipse.core.model.GrailsVersion;
import org.grails.ide.eclipse.editor.groovy.elements.GrailsProject;
import org.grails.ide.eclipse.editor.groovy.elements.GrailsWorkspaceCore;
/**
* This cache keeps track of extra domain class and controller class methods and properties.
* This cache is refreshed whenever the classpath changes
* @author Andrew Eisenberg
* @created Sep 28, 2010
*/
public class PerProjectMemberCache implements IGrailsProjectInfo {
private static final String HIBERNATE_CRITERIA_BUILDER = "grails.orm.HibernateCriteriaBuilder";
private static final String ERRORS = "org.springframework.validation.Errors";
private static final String MODEL_AND_VIEW = "org.springframework.web.servlet.ModelAndView";
private static final String LOG = "org.apache.commons.logging.Log";
private static final String HTTP_SESSION = "javax.servlet.http.HttpSession";
private static final String SERVLET_CONTEXT = "javax.servlet.ServletContext";
private static final String RESPONSE = "javax.servlet.http.HttpServletResponse";
private static final String REQUEST = "javax.servlet.http.HttpServletRequest";
private static final String GRAILS_APPLICATION_ATTRIBUTES = "org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes";
private static final String GRAILS_APPLICATION = "org.codehaus.groovy.grails.commons.GrailsApplication";
private IProject project;
private GrailsProject grailsProject;
private Map<String, ClassNode> extraControllerReferences = new HashMap<String, ClassNode>();
private Map<String, ClassNode[]> staticDomainMembers = new HashMap<String, ClassNode[]>();
private Map<String, ClassNode> nonstaticDomainMembers = new HashMap<String, ClassNode>();
private Map<String, ClassNode> dynamicDomainMembers = new HashMap<String, ClassNode>();
private Map<String, ClassNode> tagLibMembers = new HashMap<String, ClassNode>();
private Boolean isGrails2OrLater;
public IProject getProject() {
return project;
}
public void setProject(IProject project) {
this.project = project;
if (project != null) {
this.grailsProject = GrailsWorkspaceCore.get().create(project);
}
}
public void projectChanged(GrailsElementKind[] changeKinds,
IResourceDelta change) {
synchronized (GrailsCore.get().getLockForProject(project)) {
boolean foundRelevantChange = false;
for (GrailsElementKind changeKind : changeKinds) {
if (changeKind == GrailsElementKind.PROJECT ||
changeKind == GrailsElementKind.CLASSPATH) {
foundRelevantChange = true;
break;
}
}
if (foundRelevantChange) {
extraControllerReferences.clear();
staticDomainMembers.clear();
nonstaticDomainMembers.clear();
dynamicDomainMembers.clear();
}
}
}
public Map<String, ClassNode> getExtraControllerReferences() {
if (extraControllerReferences.isEmpty() && project != null) {
synchronized (GrailsCore.get().getLockForProject(project)) {
PerProjectTypeCache typeCache = GrailsCore.get().connect(project, PerProjectTypeCache.class);
if (typeCache == null) {
return Collections.emptyMap();
}
ClassNode uriClassNode = typeCache.getClassNode("java.net.URI");
extraControllerReferences.put("log", typeCache.getClassNode(LOG));
extraControllerReferences.put("actionName", VariableScope.STRING_CLASS_NODE);
extraControllerReferences.put("actionUri", uriClassNode);
extraControllerReferences.put("controllerName", VariableScope.STRING_CLASS_NODE);
extraControllerReferences.put("controllerUri", uriClassNode);
extraControllerReferences.put("flash", VariableScope.clonedMap());
extraControllerReferences.put("chainModel", VariableScope.clonedMap());
extraControllerReferences.put("grailsApplication", typeCache.getClassNode(GRAILS_APPLICATION));
extraControllerReferences.put("grailsAttributes", typeCache.getClassNode(GRAILS_APPLICATION_ATTRIBUTES));
extraControllerReferences.put("params", VariableScope.clonedMap());
extraControllerReferences.put("request", typeCache.getClassNode(REQUEST));
extraControllerReferences.put("response", typeCache.getClassNode(RESPONSE));
extraControllerReferences.put("servletContext", typeCache.getClassNode(SERVLET_CONTEXT));
extraControllerReferences.put("session", typeCache.getClassNode(HTTP_SESSION));
extraControllerReferences.put("pluginContextPath", VariableScope.STRING_CLASS_NODE);
extraControllerReferences.put("message", VariableScope.STRING_CLASS_NODE);
extraControllerReferences.put("modelAndView", typeCache.getClassNode(MODEL_AND_VIEW));
// methods
extraControllerReferences.put("getTemplateUri", uriClassNode);
extraControllerReferences.put("getViewUri", uriClassNode);
extraControllerReferences.put("bindData", VariableScope.VOID_CLASS_NODE);
extraControllerReferences.put("chain", VariableScope.VOID_CLASS_NODE);
extraControllerReferences.put("render", VariableScope.VOID_CLASS_NODE);
extraControllerReferences.put("redirect", VariableScope.VOID_CLASS_NODE);
}
}
return extraControllerReferences;
}
/**
* returns member name and a 2 element array consisting of return type and declaring type (null
* means it should be replaced by current type and list will be parameterized by current type)
* @return
*/
public Map<String, ClassNode[]> getStaticDomainMembers() {
// only do when < Grails 1.4
if (staticDomainMembers.isEmpty() && project != null && !isGrails2OrLater()) {
synchronized (GrailsCore.get().getLockForProject(project)) {
PerProjectTypeCache typeCache = GrailsCore.get().connect(project, PerProjectTypeCache.class);
if (typeCache == null) {
return Collections.emptyMap();
}
ClassNode criteriaBuilder = typeCache.getClassNode(HIBERNATE_CRITERIA_BUILDER);
staticDomainMembers.put("log", pair(typeCache.getClassNode(LOG), null));
// hmmmm....should this be commented out???
staticDomainMembers.put("createCriteria", pair(criteriaBuilder, criteriaBuilder));
staticDomainMembers.put("count", pair(VariableScope.NUMBER_CLASS_NODE, null));
staticDomainMembers.put("executeQuery", pair(VariableScope.clonedList(), criteriaBuilder));
staticDomainMembers.put("exists", pair(VariableScope.BOOLEAN_CLASS_NODE, null));
staticDomainMembers.put("findWhere", pair(null, null)); // null to be replaced with the current type
staticDomainMembers.put("findAllWhere", pair(VariableScope.clonedList(), null));
staticDomainMembers.put("get", pair(null, null)); // null to be replaced with the current type
staticDomainMembers.put("getAll", pair(VariableScope.clonedList(), null));
staticDomainMembers.put("list", pair(VariableScope.clonedList(), null));
staticDomainMembers.put("withCriteria", pair(VariableScope.clonedList(), criteriaBuilder));
staticDomainMembers.put("withTransaction", pair(VariableScope.VOID_CLASS_NODE, null));
}
}
return staticDomainMembers;
}
private boolean isGrails2OrLater() {
if (isGrails2OrLater == null) {
isGrails2OrLater = grailsProject.getEclipseGrailsVersion().compareTo(GrailsVersion.V_2_0_0) >= 0;
}
return isGrails2OrLater;
}
private ClassNode[] pair(ClassNode ret, ClassNode decl) {
return new ClassNode[] { ret, decl };
}
public Map<String, ClassNode> getNonstaticDomainMembers() {
if (nonstaticDomainMembers.isEmpty() && project != null) {
synchronized (GrailsCore.get().getLockForProject(project)) {
PerProjectTypeCache typeCache = GrailsCore.get().connect(project, PerProjectTypeCache.class);
if (typeCache == null) {
return Collections.emptyMap();
}
nonstaticDomainMembers.put("add", VariableScope.VOID_CLASS_NODE);
nonstaticDomainMembers.put("addTo", VariableScope.VOID_CLASS_NODE); // * a dynamic adder
nonstaticDomainMembers.put("clearErrors", VariableScope.VOID_CLASS_NODE);
nonstaticDomainMembers.put("hasErrors", VariableScope.BOOLEAN_CLASS_NODE);
nonstaticDomainMembers.put("ident", VariableScope.NUMBER_CLASS_NODE); // could be any type, but probably a number
// fields
nonstaticDomainMembers.put("constraints", VariableScope.clonedList());
nonstaticDomainMembers.put("properties", VariableScope.clonedList());
nonstaticDomainMembers.put("id", VariableScope.LONG_CLASS_NODE);
nonstaticDomainMembers.put("version", VariableScope.STRING_CLASS_NODE);
nonstaticDomainMembers.put("errors", typeCache.getClassNode(ERRORS));
}
}
return nonstaticDomainMembers;
}
public Map<String, ClassNode> getDynamicDomainMembers() {
synchronized (GrailsCore.get().getLockForProject(project)) {
if (dynamicDomainMembers.isEmpty() && project != null) {
dynamicDomainMembers.put("listOrderBy", VariableScope.clonedList()); // dynamic lister
dynamicDomainMembers.put("findAllBy", VariableScope.clonedList()); // dynamic finder
dynamicDomainMembers.put("findBy", null); // null to be replaced with the current type, dynamic finder
dynamicDomainMembers.put("countBy", VariableScope.NUMBER_CLASS_NODE); // dynamic counter
if (isGrails2OrLater()) {
dynamicDomainMembers.put("findOrCreateBy", null); // null to be replaced with the current type, dynamic finder
dynamicDomainMembers.put("findOrSaveBy", null); // null to be replaced with the current type, dynamic finder
}
}
return dynamicDomainMembers;
}
}
public Map<String, ClassNode> getTagLibMembers() {
if (tagLibMembers.isEmpty() && project != null) {
synchronized (GrailsCore.get().getLockForProject(project)) {
PerProjectTypeCache typeCache = GrailsCore.get().connect(project, PerProjectTypeCache.class);
if (typeCache == null) {
return Collections.emptyMap();
}
tagLibMembers.put("out", typeCache.getClassNode("java.io.Writer"));
tagLibMembers.put("grailsApplication", typeCache.getClassNode(GRAILS_APPLICATION));
tagLibMembers.put("request", typeCache.getClassNode(REQUEST));
tagLibMembers.put("response", typeCache.getClassNode(RESPONSE));
tagLibMembers.put("servletContext", typeCache.getClassNode(SERVLET_CONTEXT));
tagLibMembers.put("session", typeCache.getClassNode(HTTP_SESSION));
tagLibMembers.put("params", VariableScope.clonedMap());
tagLibMembers.put("pageScope", VariableScope.clonedMap());
tagLibMembers.put("flash", VariableScope.clonedMap());
tagLibMembers.put("controllerName", VariableScope.STRING_CLASS_NODE);
tagLibMembers.put("pluginContextPath", VariableScope.STRING_CLASS_NODE);
// should be a method with one argument
tagLibMembers.put("throwTagError", VariableScope.VOID_CLASS_NODE);
}
}
return tagLibMembers;
}
public void dispose() {
project = null;
grailsProject = null;
extraControllerReferences = new HashMap<String, ClassNode>();
staticDomainMembers = new HashMap<String, ClassNode[]>();
nonstaticDomainMembers = new HashMap<String, ClassNode>();
dynamicDomainMembers = new HashMap<String, ClassNode>();
}
}