blob: cbe083126b908d4ac3adf5f7c790afedd04c2ba5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.click.eclipse.ui.editor;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.click.eclipse.ClickPlugin;
import org.apache.click.eclipse.ClickUtils;
import org.apache.click.eclipse.preferences.ClickProjectPropertyPage.VariableModel;
import org.apache.click.eclipse.ui.editor.TemplateObject.TemplateObjectElement;
import org.apache.click.eclipse.ui.editor.TemplateObject.TemplateObjectMethod;
import org.apache.click.eclipse.ui.editor.TemplateObject.TemplateObjectProperty;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistProcessor;
/**
* {@link IContentAssistProcessor} implementation for the Velocity Template Editor.
*
* @author Naoki Takezoe
*/
public class TemplateContentAssistProcessor extends XMLContentAssistProcessor {
private IFile file;
private final Image IMAGE_DIRECTIVE = ClickPlugin.getImageDescriptor("/icons/directive.gif").createImage();
private final Image IMAGE_METHOD = ClickPlugin.getImageDescriptor("/icons/method.gif").createImage();
private final Image IMAGE_FIELD = ClickPlugin.getImageDescriptor("/icons/field.gif").createImage();
private final Image IMAGE_VAR = ClickPlugin.getImageDescriptor("/icons/localvar.gif").createImage();
private final Pattern PATTERN_SET = Pattern.compile("#set\\s*\\(\\s*\\$(.+?)\\s*=");
private final Pattern PATTERN_MACRO = Pattern.compile("#macro\\s*\\(\\s*(.+?)[\\s\\)]");
private static final Map<String, String> defaultObjects = new HashMap<String, String>();
static {
defaultObjects.put("imports", "org.apache.click.util.PageImports");
defaultObjects.put("context", "java.lang.String");
defaultObjects.put("messages", "java.util.Map");
defaultObjects.put("path", "java.lang.String");
defaultObjects.put("request", "javax.servlet.http.HttpServletRequest");
defaultObjects.put("response", "javax.servlet.http.HttpServletResponse");
defaultObjects.put("session", "org.apache.click.util.SessionMap");
};
/**
* Returns the word under the caret position.
*/
private static String getLastWord(ITextViewer textViewer, int documentPosition){
String source = textViewer.getDocument().get();
StringBuffer sb = new StringBuffer();
for(int i=0;i<documentPosition;i++){
char c = source.charAt(i);
if(Character.isWhitespace(c)){
sb.setLength(0);
} else if(c=='#' || c=='$'){
sb.setLength(0);
sb.append(c);
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Appends the completion proposal to the <code>result</code>.
*/
private static void registerProposal(List<ICompletionProposal> result, int offset,
String matchString, String replaceString, String displayString, Image image){
int position = replaceString.length();
if(replaceString.endsWith(")") && displayString.indexOf("()") < 0){
position--;
}
if(replaceString.endsWith("}") && displayString.indexOf("{}") < 0){
position--;
}
if(replaceString.startsWith(matchString)){
result.add(new CompletionProposal(
replaceString, offset - matchString.length(),
matchString.length(), position, image, displayString, null, null));
}
if(matchString.startsWith("${") && replaceString.startsWith("$") &&
!replaceString.startsWith("${")){
registerProposal(result, offset, matchString,
"${" + replaceString.substring(1), displayString, image);
}
}
/**
* Returns completion proposals.
*/
public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer, int offset) {
String matchString = getLastWord(textViewer, offset);
List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
if(!matchString.startsWith("#") && !matchString.startsWith("$")){
ICompletionProposal[] proposals = super.computeCompletionProposals(textViewer, offset);
if(proposals!=null){
for(int i=0;i<proposals.length;i++){
result.add(proposals[i]);
}
}
}
IType format = null;
List<VariableModel> preferenceObjects = null;
if(this.file != null){
// for the format object
format = ClickUtils.getFormat(file.getProject());
if(matchString.startsWith("$format.") || matchString.startsWith("${format.")){
if(format != null){
return processType(format, result, matchString, offset);
}
}
// other default objects
for(Iterator<Map.Entry<String, String>> ite = defaultObjects.entrySet().iterator(); ite.hasNext(); ){
Map.Entry<String, String> entry = ite.next();
if(matchString.startsWith("$" + entry.getKey() + ".") || matchString.startsWith("${" + entry.getKey() + ".")){
IType type = findType((String)entry.getValue());
if(type != null){
return processType(type, result, matchString, offset);
}
}
}
// prefeberce objects
ScopedPreferenceStore store = new ScopedPreferenceStore(
new ProjectScope(file.getProject()), ClickPlugin.PLUGIN_ID);
String vars = store.getString(ClickPlugin.PREF_VELOCITY_VARS);
if(vars != null && vars.length() > 0){
preferenceObjects = VariableModel.deserialize(vars);
for(int i=0;i<preferenceObjects.size();i++){
VariableModel model = preferenceObjects.get(i);
if(matchString.startsWith("$" + model.name + ".") || matchString.startsWith("${" + model.name + ".")){
IType type = findType((String)model.type);
if(type != null){
return processType(type, result, matchString, offset);
}
}
}
}
}
Map<String, TemplateObject> fields = extractPageFields();
for(Iterator<Map.Entry<String, TemplateObject>> ite = fields.entrySet().iterator(); ite.hasNext();){
Map.Entry<String, TemplateObject> entry = ite.next();
String name = entry.getKey();
if(matchString.startsWith("$" + name + ".") || matchString.startsWith("${" + name + ".")){
TemplateObject obj = (TemplateObject)entry.getValue();
if(obj.getType()!=null){
return processType(obj.getType(), result, matchString, offset);
}
}
}
if(format==null){
registerProposal(result, offset, matchString, "$format", "$format", IMAGE_VAR);
} else {
registerProposal(result, offset, matchString,
"$format", "$format - " + format.getFullyQualifiedName(), IMAGE_VAR);
}
// for page class fields
for(Iterator<Map.Entry<String, TemplateObject>> ite = fields.entrySet().iterator(); ite.hasNext();){
Map.Entry<String, TemplateObject> entry = ite.next();
String name = entry.getKey();
TemplateObject obj = (TemplateObject)entry.getValue();
registerProposal(result, offset, matchString,
"$" + name, "$" + name + " - " + obj.getTypeName(), IMAGE_FIELD);
}
// #set($xxxx)
String source = textViewer.getDocument().get().substring(0, offset);
Matcher matcher = PATTERN_SET.matcher(source);
while(matcher.find()){
String name = matcher.group(1);
registerProposal(result, offset, matchString, "$" + name, "$" + name, IMAGE_VAR);
}
// #macro(xxxx)
matcher = PATTERN_MACRO.matcher(source);
while(matcher.find()){
String name = matcher.group(1);
registerProposal(result, offset, matchString, "#" + name + "()", name, IMAGE_DIRECTIVE);
}
readMacroVM(result, offset, matchString);
registerProposal(result, offset, matchString, "$imports", "$imports - PageImports", IMAGE_VAR);
registerProposal(result, offset, matchString, "$context", "$context - String", IMAGE_VAR);
registerProposal(result, offset, matchString, "$messages", "$messages - Map", IMAGE_VAR);
registerProposal(result, offset, matchString, "$path", "$path - String", IMAGE_VAR);
registerProposal(result, offset, matchString, "$request", "$request - HttpServletRequest", IMAGE_VAR);
registerProposal(result, offset, matchString, "$response", "$response - HttpServletResponse", IMAGE_VAR);
registerProposal(result, offset, matchString, "$session", "$session - SessionMap", IMAGE_VAR);
if(preferenceObjects != null){
for(int i=0;i<preferenceObjects.size();i++){
VariableModel model = (VariableModel) preferenceObjects.get(i);
registerProposal(result, offset, matchString,
"$" + model.name, "$" + model.name + " - " + model.type, IMAGE_VAR);
}
}
registerProposal(result, offset, matchString, "#if()", "if", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#set()", "set", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#foreach()", "foreach", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#else", "else", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#elseif()", "elsif", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#end", "end", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#include()", "include", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#parse()", "parse", IMAGE_DIRECTIVE);
registerProposal(result, offset, matchString, "#macro()", "macro", IMAGE_DIRECTIVE);
return (ICompletionProposal[])result.toArray(new ICompletionProposal[result.size()]);
}
private IType findType(String className){
try {
IJavaProject project = JavaCore.create(this.file.getProject());
if(project != null){
IType type = project.findType(className);
return type;
}
} catch(Exception ex){
}
return null;
}
/**
* Read macro.vm and add macros to completion proposals.
*/
private void readMacroVM(List<ICompletionProposal> result, int offset, String matchString){
IProject project = this.file.getProject();
String folderName = ClickUtils.getWebAppRootFolder(project);
IFolder folder = project.getFolder(folderName);
IFile macroFile = folder.getFile("macro.vm");
if(macroFile!=null && macroFile.exists()){
try {
InputStream in = macroFile.getContents();
byte[] buf = new byte[in.available()];
in.read(buf);
in.close();
String source = new String(buf, macroFile.getCharset());
Matcher matcher = PATTERN_MACRO.matcher(source);
while(matcher.find()){
String name = matcher.group(1);
registerProposal(result, offset, matchString, "#" + name + "()",
name + " - macro.vm", IMAGE_DIRECTIVE);
}
} catch(Exception ex){
ClickPlugin.log(ex);
}
}
}
/**
* Returns completion proposals for the java object.
*/
private ICompletionProposal[] processType(IType type, List<ICompletionProposal> result, String matchString, int offset){
String prefix = matchString;
int index = matchString.lastIndexOf('.');
if(index >= 0){
prefix = prefix.substring(0, index);
}
TemplateObject obj = new TemplateObject(type);
obj = evaluate(obj, matchString);
if(obj != null){
TemplateObjectElement[] children = obj.getChildren();
for(int i=0;i<children.length;i++){
if(children[i] instanceof TemplateObjectMethod){
registerProposal(result, offset, matchString,
prefix + "." + children[i].getName()+"()", children[i].getDisplayName(), IMAGE_METHOD);
} else {
registerProposal(result, offset, matchString,
prefix + "." + children[i].getName(), children[i].getDisplayName(), IMAGE_FIELD);
}
}
}
return result.toArray(new ICompletionProposal[result.size()]);
}
/**
* Evaluates the given expression and returns the return type.
*
* @param obj the <code>TemplateObject</code> of the top level object
* @param expression the Velocity expression
* @return the return type of the given expression or <code>null</code>
*/
private TemplateObject evaluate(TemplateObject obj, String expression){
if(expression.endsWith(".")){
expression += "_";
}
String[] dim = expression.split("\\.");
for(int i=0; i < dim.length && obj != null; i++){
if(i == 0 || i == dim.length-1){
continue;
}
if(dim[i].endsWith(")")){
// method
String[] methodInfo = dim[i].split("\\(");
if(methodInfo.length > 0){
TemplateObjectMethod method = obj.getMethod(methodInfo[0]);
if(method != null){
obj = method.toTemplateObject();
continue;
}
}
} else {
// property
TemplateObjectProperty property = obj.getProperty(dim[i]);
if(property != null){
obj = property.toTemplateObject();
continue;
}
}
obj = null;
}
return obj;
}
/**
* Extracts public fields from the page class.
*/
private Map<String, TemplateObject> extractPageFields(){
HashMap<String, TemplateObject> map = new HashMap<String, TemplateObject>();
if(this.file != null){
try {
IType type = ClickUtils.getPageClassFromTemplate(this.file);
IJavaProject javaProject = type.getJavaProject();
IField[] fields = type.getFields();
for(int i=0;i<fields.length;i++){
if(!Flags.isPublic(fields[i].getFlags())){
continue;
}
String className = ClickUtils.removeTypeParameter(Signature.toString(fields[i].getTypeSignature()));
// primitive types
if(ClickUtils.isPrimitive(className)){
TemplateObject obj = new TemplateObject(className);
map.put(fields[i].getElementName(), obj);
continue;
}
// object types
className = ClickUtils.resolveClassName(type, className);
IType fieldType = javaProject.findType(className);
if(fieldType != null){
TemplateObject obj = new TemplateObject(fieldType);
map.put(fields[i].getElementName(), obj);
}
}
} catch(Exception ex){
//ClickPlugin.log(ex);
}
}
return map;
}
/**
* Sets the editing filr in the editor.
*
* @param file the editing file
*/
public void setFile(IFile file){
this.file = file;
}
/**
* Releases internal resources such as icons.
*/
public void release() {
IMAGE_DIRECTIVE.dispose();
IMAGE_METHOD.dispose();
IMAGE_FIELD.dispose();
IMAGE_VAR.dispose();
super.release();
}
}