/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.completion.model;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.util.containers.HashSet;
import gw.lang.parser.GosuParserTypes;
import gw.lang.parser.IBlockClass;
import gw.lang.reflect.IAttributedFeatureInfo;
import gw.lang.reflect.IMetaType;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.Modifier;
import gw.lang.reflect.TypeInfoUtil;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.TypeName;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.module.IModule;
import gw.plugin.ij.util.ExceptionUtil;
import gw.util.IFeatureFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
public class BeanTree implements Comparable<BeanTree> {
private BeanInfoNode _node;
private BeanTree _parent;
private final IType _whosaskin;
private List<BeanTree> _children;
private boolean _bIncludeStaticMembers;
private IFeatureFilter _filter;
private IModule _moduleCtx;
public BeanTree(IType classBean, IType whosaskin, boolean bIncludeStaticMembers, IFeatureFilter filter, IModule moduleCtx) {
_whosaskin = whosaskin;
_node = new BeanInfoNode(classBean);
_node.setDisplayName("");
_bIncludeStaticMembers = bIncludeStaticMembers;
_filter = filter;
_moduleCtx = moduleCtx;
initializeChildren();
}
private BeanTree(IMethodInfo descriptor, @NotNull BeanTree parent) {
this._whosaskin = parent._whosaskin;
this._node = new MethodNode(descriptor);
this._parent = parent;
}
private BeanTree(IPropertyInfo descriptor, @NotNull BeanTree parent) {
this._whosaskin = parent._whosaskin;
this._node = new PropertyInfoNode(descriptor);
this._parent = parent;
}
// ----------------------------------------------------------------------------
public BeanInfoNode getBeanNode() {
return _node;
}
// ----------------------------------------------------------------------------
protected void initializeChildren() {
if (_children != null) {
return;
}
_children = Lists.newArrayList();
IType type = _node.getType();
if (!TypeSystem.isBeanType(type) &&
!(type instanceof IMetaType) &&
type != GosuParserTypes.BOOLEAN_TYPE() &&
type != GosuParserTypes.STRING_TYPE() &&
type != GosuParserTypes.NUMBER_TYPE() &&
!type.isArray()) {
return;
}
try {
if (type.isArray()) {
addPropertyAndMethodNodes(type.getTypeInfo());
type = type.getComponentType();
}
ITypeInfo beanInfo = null;
try {
// Note retrieving the typeinfo may throw an exception if the
// corresponding
// class cannot be loaded (e.g., when the class is not in Studio's local
// classpath, but is on the server's). We must handle this gracefully.
beanInfo = type.getTypeInfo();
} catch (Throwable t) {
t.printStackTrace();
}
if (beanInfo == null) {
return;
}
addPropertyAndMethodNodes(beanInfo);
addInnerClassNodes(beanInfo);
// Collections.sort(_children);
} catch (ProcessCanceledException e) {
throw e;
} catch (Exception e) {
if (ExceptionUtil.isWrappedCanceled(e)) {
throw new ProcessCanceledException(e.getCause());
}
throw new RuntimeException(e);
}
}
private void addPropertyAndMethodNodes(@NotNull ITypeInfo beanInfo) {
TypeSystem.pushModule(_moduleCtx);
try {
addPropertyNodes(beanInfo);
addMethodNodes(beanInfo);
} finally {
TypeSystem.popModule(_moduleCtx);
}
}
private void addMethodNodes(@NotNull ITypeInfo beanInfo) {
final List<? extends IMethodInfo> methods = beanInfo instanceof IRelativeTypeInfo ? ((IRelativeTypeInfo) beanInfo).getMethods(_whosaskin) : beanInfo.getMethods();
final Set<String> signatures = new HashSet<String>();
if (!methods.isEmpty()) {
for (IMethodInfo method : methods) {
if (isHidden(beanInfo, method)) {
continue; // Don't expose hidden methods in the composer.
}
if (!mutualExclusiveStaticFilter(beanInfo, method)) {
continue;
}
final String signature = TypeInfoUtil.getMethodSignature(method);
if (!signatures.contains(signature)) {
_children.add(new BeanTree(method, this));
signatures.add(signature);
}
}
}
}
private void addPropertyNodes(@NotNull ITypeInfo beanInfo) {
List<? extends IPropertyInfo> properties = beanInfo instanceof IRelativeTypeInfo ? ((IRelativeTypeInfo) beanInfo).getProperties(_whosaskin) : beanInfo.getProperties();
Set<String> propertyNames = new HashSet<String>();
for (IPropertyInfo property : properties) {
if (isHidden(beanInfo, property)) {
// Don't expose hidden properties in the composer.
continue;
}
if (!mutualExclusiveStaticFilter(beanInfo, property)) {
continue;
}
String displayName = property.getDisplayName();
if (!propertyNames.contains(displayName)) {
_children.add(new BeanTree(property, this));
propertyNames.add(displayName);
}
}
}
private void addInnerClassNodes(@NotNull ITypeInfo beanInfo) {
if (beanInfo.getOwnersType() instanceof IMetaType) {
IType ownersType = ((IMetaType) beanInfo.getOwnersType()).getType();
if (ownersType instanceof IGosuClass) {
for (Entry<CharSequence, ? extends IGosuClass> innerClass : ((IGosuClass) ownersType).getInnerClassesMap().entrySet()) {
IGosuClass gosuClass = innerClass.getValue();
if (!(gosuClass instanceof IBlockClass)) {
boolean accessible = false;
switch (gosuClass.getTypeInfo().getAccessibilityForType(_whosaskin)) {
case PUBLIC:
if (Modifier.isPublic(gosuClass.getModifiers())) {
accessible = true;
}
break;
case PROTECTED:
if (Modifier.isProtected(gosuClass.getModifiers()) || Modifier.isPublic(gosuClass.getModifiers())) {
accessible = true;
}
break;
case INTERNAL:
if (!Modifier.isPrivate(gosuClass.getModifiers())) {
accessible = true;
}
break;
case PRIVATE:
accessible = true;
break;
}
if (accessible) {
TypeInPackageType type = new TypeInPackageType(new TypeName(gosuClass));
TypePropertyInfo descriptor = new TypePropertyInfo(ownersType.getTypeInfo(), type);
_children.add(new BeanTree(descriptor, this));
}
}
}
} else if (ownersType instanceof IJavaType) {
List<IJavaType> innerClasses = ((IJavaType) ownersType).getInnerClasses();
for (IJavaType innerType : innerClasses) {
//TODO-dp filter anonymous clases if returned ?
// if(!innerType.isAnonymous()) {
TypeInPackageType type = new TypeInPackageType(new TypeName(innerType));
TypePropertyInfo descriptor = new TypePropertyInfo(ownersType.getTypeInfo(), type);
_children.add(new BeanTree(descriptor, this));
// }
}
}
}
}
private boolean isHidden(@NotNull ITypeInfo beanInfo, @NotNull IAttributedFeatureInfo feature) {
return
feature.isHidden() ||
!feature.isScriptable() ||
feature.isDeprecated() ||
isInternal(feature) ||
(getFeatureFilter() != null && !getFeatureFilter().acceptFeature(beanInfo.getOwnersType(), feature));
}
private boolean mutualExclusiveStaticFilter(@NotNull ITypeInfo beanInfo, @NotNull IAttributedFeatureInfo descriptor) {
if (beanInfo.getOwnersType() instanceof IMetaType || includeStaticMembers()) {
return descriptor.isStatic();
}
return true;
}
private boolean isInternal(@NotNull IAttributedFeatureInfo feature) {
return feature instanceof IMethodInfo && feature.getDisplayName().startsWith("@");
}
public BeanTree getChildAt(int iChildIndex) {
return _children.get(iChildIndex);
}
public int getChildCount() {
initializeChildren();
return _children.size();
}
public BeanTree getParent() {
return _parent;
}
public int compareTo(@NotNull BeanTree o) {
if (_node == null) {
return o._node == null ? 0 : -1;
} else {
if (o._node == null) {
return 1;
}
return _node.compareTo(o._node);
}
}
public List<BeanTree> getChildren() {
return _children;
}
@Nullable
private IFeatureFilter getFeatureFilter() {
if (_filter != null) {
return _filter;
}
return _parent != null ? _parent.getFeatureFilter() : null;
}
private boolean includeStaticMembers() {
return _bIncludeStaticMembers;
}
public String toString() {
return _node.toString();
}
public IType getWhosAsking() {
return _whosaskin;
}
}