package org.rubypeople.rdt.internal.core.hierarchy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.rubypeople.rdt.core.IOpenable;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.codeassist.RubyElementRequestor;
import org.rubypeople.rdt.internal.core.LogicalType;
public class HierarchyResolver {
private static final int RECURSE_DEPTH_BAILOUT = 50;
private boolean superTypesOnly;
private HierarchyBuilder builder;
private HashSet<String> visitedTypes;
public HierarchyResolver(Map options, HierarchyBuilder builder) {
this.builder = builder;
}
public void resolve(IOpenable[] openables, HashSet<String> localTypes, IProgressMonitor monitor) {
try {
int openablesLength = openables.length;
IType focus = this.builder.getType();
for (int i = 0; i < openablesLength; i++) {
IOpenable openable = openables[i];
if (openable instanceof org.rubypeople.rdt.core.IRubyScript) {
org.rubypeople.rdt.core.IRubyScript cu = (org.rubypeople.rdt.core.IRubyScript)openable;
// Grab the types from the script and then connect them up!
IType[] types = cu.getAllTypes();
for (int j = 0; j < types.length; j++) {
IType type = types[j];
if (focusIsInHierarchy(focus, type, 0)) { // if it's our focus type, or a subclass
try {
this.visitedTypes = new HashSet<String>();
reportHierarchy(type);
visitedTypes.clear();
} catch (RubyModelException e) {
// ignore
}
}
}
}
}
} catch (ClassCastException e){ // work-around for 1GF5W1S - can happen in case duplicates are fed to the hierarchy with binaries hiding sources
} catch (RubyModelException e){
} finally {
reset();
}
}
private boolean focusIsInHierarchy(IType focus, IType type, int currentDepth) throws RubyModelException {
if (currentDepth > RECURSE_DEPTH_BAILOUT)
{
RubyCore.log(IStatus.ERROR, "Current recurse depth is " + currentDepth + " for resolving type hierarchy for type " + type.getFullyQualifiedName(), new IllegalStateException().fillInStackTrace());
return false;
}
if (focus == null || type == null) return false;
if (type.getFullyQualifiedName().equals(focus.getFullyQualifiedName())) return true; // type is focus
return focusIsInHierarchy(focus, findSuperClass(type), ++currentDepth);
}
private void reportHierarchy(IType type) throws RubyModelException {
visitedTypes.add(type.getFullyQualifiedName());
IType superclass;
if (type.isModule()){ // do not connect modules to Object
superclass = null;
} else {
superclass = findSuperClass(type);
}
IType[] superinterfaces = findSuperInterfaces(type);
this.builder.connect(type, superclass, superinterfaces);
if (type.isClass() && superclass != null) {
if (visitedTypes.contains(superclass.getFullyQualifiedName())) {
throw new IllegalStateException("Invalid type hierarchy: Loop in tree. Class: " + type.getFullyQualifiedName() + ", superclass: " + superclass.getFullyQualifiedName()); // Safety valve for recursive type hierarchies
}
reportHierarchy(superclass);
}
}
private IType[] findSuperInterfaces(IType type) throws RubyModelException {
String[] names = type.getIncludedModuleNames();
List<IType> types = new ArrayList<IType>();
for (int i = 0; i < names.length; i++) {
IType logical = getLogicalType(type, names[i]);
if (logical == null) {
// try to see if full name is in same namespace.
String namespace = type.getFullyQualifiedName().substring(0, type.getFullyQualifiedName().length() - type.getElementName().length());
logical = getLogicalType(type, namespace + names[i]);
if (logical == null) continue;
}
types.add(logical);
}
return (IType[]) types.toArray(new IType[types.size()]);
}
private IType findSuperClass(IType type) throws RubyModelException {
String name = type.getSuperclassName();
if (name == null) return null;
return getLogicalType(type, name);
}
private IType getLogicalType(IType type, String name) {
RubyElementRequestor requestor = new RubyElementRequestor(type.getRubyScript());
IType[] types = requestor.findType(name);
if (types == null || types.length == 0) return null;
return new LogicalType(types);
}
private void reset() {
// this.focusType = null;
this.superTypesOnly = false;
}
public void resolve(IType type) {
org.rubypeople.rdt.core.IRubyScript cu = type.getRubyScript();
HashSet<String> localTypes = new HashSet<String>();
localTypes.add(cu.getPath().toString());
this.superTypesOnly = true;
resolve(new IOpenable[] {cu}, localTypes, null);
}
}