blob: 226dcc24380d1df1c80653ba159440294d16af78 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.jetbrains.plugins.groovy.dsl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.dsl.dsltop.GdslMembersProvider;
import org.jetbrains.plugins.groovy.dsl.holders.CompoundMembersHolder;
import org.jetbrains.plugins.groovy.dsl.holders.CustomMembersHolder;
import org.jetbrains.plugins.groovy.dsl.holders.DeclarationType;
import org.jetbrains.plugins.groovy.dsl.holders.NonCodeMembersHolder;
import org.jetbrains.plugins.groovy.dsl.toplevel.ClassContextFilter;
import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.*;
/**
* @author peter
*/
public class CustomMembersGenerator extends GroovyObjectSupport implements GdslMembersHolderConsumer {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.dsl.CustomMembersGenerator");
private static final GdslMembersProvider[] PROVIDERS = GdslMembersProvider.EP_NAME.getExtensions();
public static final String THROWS = "throws";
private FList<Map> myDeclarations = FList.emptyList();
private final Project myProject;
private final CompoundMembersHolder myDepot = new CompoundMembersHolder();
private final GroovyClassDescriptor myDescriptor;
@Nullable private final Map<String, List> myBindings;
private final PsiClass myPsiClass;
public CustomMembersGenerator(@NotNull GroovyClassDescriptor descriptor, @Nullable PsiType type, @Nullable Map<String, List> bindings) {
myDescriptor = descriptor;
myBindings = bindings;
myProject = descriptor.getProject();
myPsiClass = type instanceof PsiClassType ? ((PsiClassType)type).resolve() : null;
}
@Override
public PsiElement getPlace() {
return myDescriptor.getPlace();
}
@Override
@Nullable
public PsiClass getClassType() {
return getPsiClass();
}
@Override
public PsiType getPsiType() {
return myDescriptor.getPsiType();
}
@Nullable
@Override
public PsiClass getPsiClass() {
return myPsiClass;
}
@Override
public GlobalSearchScope getResolveScope() {
return myDescriptor.getResolveScope();
}
@Override
public Project getProject() {
return myProject;
}
@Nullable
public CustomMembersHolder getMembersHolder() {
if (!myDeclarations.isEmpty()) {
addMemberHolder(new CustomMembersHolder() {
@Override
public boolean processMembers(GroovyClassDescriptor descriptor, PsiScopeProcessor processor, ResolveState state) {
return NonCodeMembersHolder.generateMembers(ContainerUtil.reverse(myDeclarations), descriptor.justGetPlaceFile()).processMembers(
descriptor, processor, state);
}
});
}
return myDepot;
}
@Override
public void addMemberHolder(CustomMembersHolder holder) {
myDepot.addHolder(holder);
}
private Object[] constructNewArgs(Object[] args) {
final Object[] newArgs = new Object[args.length + 1];
//noinspection ManualArrayCopy
for (int i = 0; i < args.length; i++) {
newArgs[i] = args[i];
}
newArgs[args.length] = this;
return newArgs;
}
/** **********************************************************
Methods to add new behavior
*********************************************************** */
public void property(Map<Object, Object> args) {
if (args == null) return;
String name = (String)args.get("name");
Object type = args.get("type");
Object doc = args.get("doc");
Object docUrl = args.get("docUrl");
Boolean isStatic = (Boolean)args.get("isStatic");
Map<Object, Object> getter = new HashMap<Object, Object>();
getter.put("name", GroovyPropertyUtils.getGetterNameNonBoolean(name));
getter.put("type", type);
getter.put("isStatic", isStatic);
getter.put("doc", doc);
getter.put("docUrl", docUrl);
method(getter);
Map<Object, Object> setter = new HashMap<Object, Object>();
setter.put("name", GroovyPropertyUtils.getSetterName(name));
setter.put("type", "void");
setter.put("isStatic", isStatic);
setter.put("doc", doc);
setter.put("docUrl", docUrl);
final HashMap<Object, Object> param = new HashMap<Object, Object>();
param.put(name, type);
setter.put("params", param);
method(setter);
}
public void constructor(Map<Object, Object> args) {
if (args == null) return;
args.put("constructor", true);
method(args);
}
@SuppressWarnings("MethodMayBeStatic")
public ParameterDescriptor parameter(Map args) {
return new ParameterDescriptor(args, myDescriptor.justGetPlaceFile());
}
public void method(Map<Object, Object> args) {
if (args == null) return;
args = ContainerUtil.newLinkedHashMap(args);
parseMethod(args);
args.put("declarationType", DeclarationType.METHOD);
myDeclarations = myDeclarations.prepend(args);
}
public void methodCall(Closure<Map<Object, Object>> generator) {
PsiElement place = myDescriptor.getPlace();
PsiElement parent = place.getParent();
if (isMethodCall(place, parent)) {
assert parent instanceof GrMethodCall && place instanceof GrReferenceExpression;
GrReferenceExpression ref = (GrReferenceExpression)place;
PsiType[] argTypes = PsiUtil.getArgumentTypes(ref, false);
if (argTypes == null) return;
String[] types = new String[argTypes.length];
ContainerUtil.map(argTypes, new Function<PsiType, Object>() {
@Override
public Object fun(PsiType type) {
return type.getCanonicalText();
}
}, types);
generator.setDelegate(this);
HashMap<String, Object> args = new HashMap<String, Object>();
args.put("name", ref.getReferenceName());
args.put("argumentTypes", types);
generator.call(args);
}
}
private static boolean isMethodCall(PsiElement place, PsiElement parent) {
return place instanceof GrReferenceExpression &&
parent instanceof GrMethodCall &&
((GrMethodCall)parent).getInvokedExpression() == place;
}
@SuppressWarnings("unchecked")
private static void parseMethod(Map args) {
String type = stringifyType(args.get("type"));
args.put("type", type);
Object namedParams = args.get("namedParams");
if (namedParams instanceof List) {
LinkedHashMap newParams = new LinkedHashMap();
newParams.put("args", namedParams);
Object oldParams = args.get("params");
if (oldParams instanceof Map) {
newParams.putAll((Map)oldParams);
}
args.put("params", newParams);
}
//noinspection unchecked
Object params = args.get("params");
if (params instanceof Map) {
boolean first = true;
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>)params).entrySet()) {
Object value = entry.getValue();
if (!first || !(value instanceof List)) {
entry.setValue(stringifyType(value));
}
first = false;
}
}
final Object toThrow = args.get(THROWS);
if (toThrow instanceof List) {
final ArrayList<String> list = new ArrayList<String>();
for (Object o : (List)toThrow) {
list.add(stringifyType(o));
}
args.put(THROWS, list);
}
else if (toThrow != null) {
args.put(THROWS, Collections.singletonList(stringifyType(toThrow)));
}
}
@SuppressWarnings("UnusedDeclaration")
public void closureInMethod(Map<Object, Object> args) {
if (args == null) return;
args = ContainerUtil.newLinkedHashMap(args);
parseMethod(args);
final Object method = args.get("method");
if (method instanceof Map) {
parseMethod((Map)method);
}
args.put("declarationType", DeclarationType.CLOSURE);
myDeclarations = myDeclarations.prepend(args);
}
public void variable(Map<Object, Object> args) {
if (args == null) return;
args = ContainerUtil.newLinkedHashMap(args);
parseVariable(args);
myDeclarations = myDeclarations.prepend(args);
}
private static void parseVariable(Map<Object, Object> args) {
String type = stringifyType(args.get("type"));
args.put("type", type);
args.put("declarationType", DeclarationType.VARIABLE);
}
private static String stringifyType(Object type) {
if (type == null) return CommonClassNames.JAVA_LANG_OBJECT;
if (type instanceof Closure) return GroovyCommonClassNames.GROOVY_LANG_CLOSURE;
if (type instanceof Map) return CommonClassNames.JAVA_UTIL_MAP;
if (type instanceof Class) return ((Class)type).getName();
String s = type.toString();
LOG.assertTrue(!s.startsWith("? extends"), s);
LOG.assertTrue(!s.contains("?extends"), s);
LOG.assertTrue(!s.contains("<null."), s);
LOG.assertTrue(!s.startsWith("null."), s);
LOG.assertTrue(!(s.contains(",") && !s.contains("<")), s);
return s;
}
@SuppressWarnings("UnusedDeclaration")
@Nullable
public Object methodMissing(String name, Object args) {
final Object[] newArgs = constructNewArgs((Object[])args);
// Get other DSL methods from extensions
for (GdslMembersProvider provider : PROVIDERS) {
final List<MetaMethod> variants = DefaultGroovyMethods.getMetaClass(provider).respondsTo(provider, name, newArgs);
if (variants.size() == 1) {
return InvokerHelper.invokeMethod(provider, name, newArgs);
}
}
return null;
}
@SuppressWarnings("UnusedDeclaration")
@Nullable
public Object propertyMissing(String name) {
if (myBindings != null) {
final List list = myBindings.get(name);
if (list != null) {
return list;
}
}
return null;
}
public static class ParameterDescriptor {
public final String name;
public final NamedArgumentDescriptor descriptor;
private ParameterDescriptor(Map args, PsiElement context) {
name = (String)args.get("name");
final String typeText = stringifyType(args.get("type"));
Object doc = args.get("doc");
descriptor = new NamedArgumentDescriptor(new GdslNamedParameter(name, doc instanceof String ? (String)doc : null, context, typeText)) {
@Override
public boolean checkType(@NotNull PsiType type, @NotNull GroovyPsiElement context) {
return typeText == null || ClassContextFilter.isSubtype(type, context.getContainingFile(), typeText);
}
};
descriptor.setPriority(NamedArgumentDescriptor.Priority.ALWAYS_ON_TOP);
}
}
public static class GdslNamedParameter extends FakePsiElement {
private final String myName;
public final String docString;
private final PsiElement myParent;
@Nullable public final String myParameterTypeText;
public GdslNamedParameter(String name, String doc, @NotNull PsiElement parent, @Nullable String type) {
myName = name;
this.docString = doc;
myParent = parent;
myParameterTypeText = type;
}
@Override
public PsiElement getParent() {
return myParent;
}
@Override
public String getName() {
return myName;
}
}
}