/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.rubypeople.rdt.internal.core.hierarchy;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SafeRunner;
import org.rubypeople.rdt.core.ElementChangedEvent;
import org.rubypeople.rdt.core.IElementChangedListener;
import org.rubypeople.rdt.core.ILoadpathEntry;
import org.rubypeople.rdt.core.IOpenable;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyElementDelta;
import org.rubypeople.rdt.core.IRubyModelStatusConstants;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.ISourceFolder;
import org.rubypeople.rdt.core.ISourceFolderRoot;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.ITypeHierarchy;
import org.rubypeople.rdt.core.ITypeHierarchyChangedListener;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.core.WorkingCopyOwner;
import org.rubypeople.rdt.core.search.IRubySearchScope;
import org.rubypeople.rdt.core.search.SearchEngine;
import org.rubypeople.rdt.internal.core.Openable;
import org.rubypeople.rdt.internal.core.Region;
import org.rubypeople.rdt.internal.core.RubyElement;
import org.rubypeople.rdt.internal.core.RubyModelStatus;
import org.rubypeople.rdt.internal.core.RubyProject;
import org.rubypeople.rdt.internal.core.RubyScript;
import org.rubypeople.rdt.internal.core.SourceFolder;
import org.rubypeople.rdt.internal.core.TypeVector;
import org.rubypeople.rdt.internal.core.util.Messages;
import org.rubypeople.rdt.internal.core.util.Util;
/**
* @see ITypeHierarchy
*/
public class TypeHierarchy implements ITypeHierarchy, IElementChangedListener
{
public static boolean DEBUG = false;
static final byte VERSION = 0x0000;
// SEPARATOR
static final byte SEPARATOR1 = '\n';
static final byte SEPARATOR2 = ',';
static final byte SEPARATOR3 = '>';
static final byte SEPARATOR4 = '\r';
// general info
static final byte COMPUTE_SUBTYPES = 0x0001;
// type info
static final byte CLASS = 0x0000;
static final byte INTERFACE = 0x0001;
static final byte COMPUTED_FOR = 0x0002;
static final byte ROOT = 0x0004;
// cst
static final byte[] NO_FLAGS = new byte[] {};
static final int SIZE = 10;
/**
* The Ruby Project in which the hierarchy is being built - this provides the context for determining a classpath
* and namelookup rules. Possibly null.
*/
protected IRubyProject project;
/**
* The type the hierarchy was specifically computed for, possibly null.
*/
protected IType focusType;
/*
* The working copies that take precedence over original compilation units
*/
protected IRubyScript[] workingCopies;
protected Map<IType, IType> classToSuperclass;
protected Map<IType, IType[]> typeToSuperModules;
protected Map<IType, TypeVector> typeToSubtypes;
protected Map<IType, Integer> typeFlags;
protected TypeVector rootClasses = new TypeVector();
protected ArrayList<IType> modules = new ArrayList<IType>(10);
public ArrayList<String> missingTypes = new ArrayList<String>(4);
protected static final IType[] NO_TYPE = new IType[0];
/**
* The progress monitor to report work completed too.
*/
protected IProgressMonitor progressMonitor = null;
/**
* Change listeners - null if no one is listening.
*/
protected ArrayList<ITypeHierarchyChangedListener> changeListeners = null;
/*
* A map from Openables to ArrayLists of ITypes
*/
public Map<IOpenable, ArrayList<IType>> files = null;
/**
* A region describing the packages considered by this hierarchy. Null if not activated.
*/
protected Region packageRegion = null;
/**
* A region describing the projects considered by this hierarchy. Null if not activated.
*/
protected Region projectRegion = null;
/**
* Whether this hierarchy should contains subtypes.
*/
protected boolean computeSubtypes;
/**
* The scope this hierarchy should restrain itsef in.
*/
IRubySearchScope scope;
/*
* Whether this hierarchy needs refresh
*/
public boolean needsRefresh = true;
/*
* Collects changes to types
*/
protected ChangeCollector changeCollector;
/**
* Creates an empty TypeHierarchy
*/
public TypeHierarchy()
{
// Creates an empty TypeHierarchy
}
/**
* Creates a TypeHierarchy on the given type.
*/
public TypeHierarchy(IType type, IRubyScript[] workingCopies, IRubyProject project, boolean computeSubtypes)
{
this(type, workingCopies, SearchEngine.createRubySearchScope(new IRubyElement[] { project }), computeSubtypes);
this.project = project;
}
/**
* Creates a TypeHierarchy on the given type.
*/
public TypeHierarchy(IType type, IRubyScript[] workingCopies, IRubySearchScope scope, boolean computeSubtypes)
{
this.focusType = type == null ? null : (IType) ((RubyElement) type).unresolved(); // unsure the focus type is
// unresolved (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=92357)
this.workingCopies = workingCopies;
this.computeSubtypes = computeSubtypes;
this.scope = scope;
}
/**
* Initializes the file, package and project regions
*/
protected void initializeRegions()
{
IType[] allTypes = getAllTypes();
for (int i = 0; i < allTypes.length; i++)
{
IType type = allTypes[i];
Openable o = (Openable) ((RubyElement) type).getOpenableParent();
if (o != null)
{
ArrayList<IType> types = (ArrayList<IType>) this.files.get(o);
if (types == null)
{
types = new ArrayList<IType>();
this.files.put(o, types);
}
types.add(type);
}
ISourceFolder pkg = type.getSourceFolder();
this.packageRegion.add(pkg);
IRubyProject declaringProject = type.getRubyProject();
if (declaringProject != null)
{
this.projectRegion.add(declaringProject);
}
checkCanceled();
}
}
/**
* Adds all of the elements in the collection to the list if the element is not already in the list.
*/
private void addAllCheckingDuplicates(ArrayList<IType> list, IType[] collection)
{
for (int i = 0; i < collection.length; i++)
{
IType element = collection[i];
if (!list.contains(element))
{
list.add(element);
}
}
}
/**
* Adds the type to the collection of interfaces.
*/
protected void addModule(IType type)
{
this.modules.add(type);
}
/**
* Adds the type to the collection of root classes if the classes is not already present in the collection.
*/
protected void addRootClass(IType type)
{
if (this.rootClasses.contains(type))
return;
this.rootClasses.add(type);
}
/**
* Adds the given subtype to the type.
*/
protected void addSubtype(IType type, IType subtype)
{
TypeVector subtypes = this.typeToSubtypes.get(type);
if (subtypes == null)
{
subtypes = new TypeVector();
this.typeToSubtypes.put(type, subtypes);
}
if (!subtypes.contains(subtype))
{
subtypes.add(subtype);
}
}
/**
* @see ITypeHierarchy
*/
public synchronized void addTypeHierarchyChangedListener(ITypeHierarchyChangedListener listener)
{
ArrayList<ITypeHierarchyChangedListener> listeners = this.changeListeners;
if (listeners == null)
{
this.changeListeners = listeners = new ArrayList<ITypeHierarchyChangedListener>();
}
// register with RubyCore to get Ruby element delta on first listener added
if (listeners.size() == 0)
{
RubyCore.addElementChangedListener(this);
}
// add listener only if it is not already present
if (listeners.indexOf(listener) == -1)
{
listeners.add(listener);
}
}
private static Integer bytesToFlags(byte[] bytes)
{
if (bytes != null && bytes.length > 0)
{
return new Integer(new String(bytes));
}
else
{
return null;
}
}
/**
* cacheFlags.
*/
public void cacheFlags(IType type, int flags)
{
this.typeFlags.put(type, new Integer(flags));
}
/**
* Caches the handle of the superclass for the specified type. As a side effect cache this type as a subtype of the
* superclass.
*/
protected void cacheSuperclass(IType type, IType superclass)
{
if (superclass != null)
{
this.classToSuperclass.put(type, superclass);
addSubtype(superclass, type);
}
}
/**
* Caches all of the supermodules that are specified for the type.
*/
protected void cacheSuperModules(IType type, IType[] supermodules)
{
this.typeToSuperModules.put(type, supermodules);
for (int i = 0; i < supermodules.length; i++)
{
IType supermodule = supermodules[i];
if (supermodule != null)
{
addModule(supermodule);
addSubtype(supermodule, type);
}
}
}
/**
* Checks with the progress monitor to see whether the creation of the type hierarchy should be canceled. Should be
* regularly called so that the user can cancel.
*
* @exception OperationCanceledException
* if cancelling the operation has been requested
* @see IProgressMonitor#isCanceled
*/
protected void checkCanceled()
{
if (this.progressMonitor != null && this.progressMonitor.isCanceled())
{
throw new OperationCanceledException();
}
}
/**
* Compute this type hierarchy.
*/
protected void compute() throws RubyModelException, CoreException
{
if (this.focusType != null)
{
HierarchyBuilder builder = new IndexBasedHierarchyBuilder(this, this.scope);
builder.build(this.computeSubtypes);
} // else a RegionBasedTypeHierarchy should be used
}
/**
* @see ITypeHierarchy
*/
public boolean contains(IType type)
{
// classes
if (this.classToSuperclass.get(type) != null)
{
return true;
}
// root classes
if (this.rootClasses.contains(type))
return true;
// interfaces
if (this.modules.contains(type))
return true;
return false;
}
/**
* Determines if the change effects this hierarchy, and fires change notification if required.
*/
public void elementChanged(ElementChangedEvent event)
{
// type hierarchy change has already been fired
if (this.needsRefresh)
return;
if (isAffected(event.getDelta()))
{
this.needsRefresh = true;
fireChange();
}
}
/**
* @see ITypeHierarchy
*/
public boolean exists()
{
if (!this.needsRefresh)
return true;
return (this.focusType == null || this.focusType.exists()) && this.rubyProject().exists();
}
/**
* Notifies listeners that this hierarchy has changed and needs refreshing. Note that listeners can be removed as we
* iterate through the list.
*/
@SuppressWarnings("unchecked")
public void fireChange()
{
ArrayList<ITypeHierarchyChangedListener> listeners = this.changeListeners;
if (listeners == null)
{
return;
}
if (DEBUG)
{
System.out.println("FIRING hierarchy change [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
if (this.focusType != null)
{
System.out
.println(" for hierarchy focused on " + ((RubyElement) this.focusType).toStringWithAncestors()); //$NON-NLS-1$
}
}
// clone so that a listener cannot have a side-effect on this list when being notified
listeners = (ArrayList<ITypeHierarchyChangedListener>) listeners.clone();
for (int i = 0; i < listeners.size(); i++)
{
final ITypeHierarchyChangedListener listener = listeners.get(i);
SafeRunner.run(new ISafeRunnable()
{
public void handleException(Throwable exception)
{
Util.log(exception, "Exception occurred in listener of Type hierarchy change notification"); //$NON-NLS-1$
}
public void run() throws Exception
{
listener.typeHierarchyChanged(TypeHierarchy.this);
}
});
}
}
private static byte[] flagsToBytes(Integer flags)
{
if (flags != null)
{
return flags.toString().getBytes();
}
else
{
return NO_FLAGS;
}
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllClasses()
{
TypeVector classes = this.rootClasses.copy();
for (IType type : this.classToSuperclass.keySet())
{
classes.add(type);
}
return classes.elements();
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllModules()
{
IType[] collection = new IType[this.modules.size()];
this.modules.toArray(collection);
return collection;
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllSubtypes(IType type)
{
return getAllSubtypesForType(type);
}
/**
* @see #getAllSubtypes(IType)
*/
private IType[] getAllSubtypesForType(IType type)
{
ArrayList<IType> subTypes = new ArrayList<IType>();
getAllSubtypesForType0(type, subTypes);
IType[] subClasses = new IType[subTypes.size()];
subTypes.toArray(subClasses);
return subClasses;
}
/**
*/
private void getAllSubtypesForType0(IType type, ArrayList<IType> subs)
{
IType[] subTypes = getSubtypesForType(type);
if (subTypes.length != 0)
{
for (IType subType : subTypes)
{
subs.add(subType);
getAllSubtypesForType0(subType, subs);
}
}
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllSuperclasses(IType type)
{
IType superclass = getSuperclass(type);
TypeVector supers = new TypeVector();
while (superclass != null)
{
supers.add(superclass);
superclass = getSuperclass(superclass);
}
return supers.elements();
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllSuperModules(IType type)
{
ArrayList<IType> supers = new ArrayList<IType>();
if (this.typeToSuperModules.get(type) == null)
{
return NO_TYPE;
}
getAllSuperModules0(type, supers);
IType[] supermodules = new IType[supers.size()];
supers.toArray(supermodules);
return supermodules;
}
private void getAllSuperModules0(IType type, ArrayList<IType> supers)
{
IType[] superinterfaces = this.typeToSuperModules.get(type);
if (superinterfaces != null && superinterfaces.length != 0)
{
addAllCheckingDuplicates(supers, superinterfaces);
for (int i = 0; i < superinterfaces.length; i++)
{
getAllSuperModules0(superinterfaces[i], supers);
}
}
IType superclass = this.classToSuperclass.get(type);
if (superclass != null)
{
getAllSuperModules0(superclass, supers);
}
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllSupertypes(IType type)
{
ArrayList<IType> supers = new ArrayList<IType>();
if (this.typeToSuperModules.get(type) == null)
{
return NO_TYPE;
}
getAllSupertypes0(type, supers);
IType[] supertypes = new IType[supers.size()];
supers.toArray(supertypes);
return supertypes;
}
private void getAllSupertypes0(IType type, ArrayList<IType> supers)
{
IType[] superinterfaces = this.typeToSuperModules.get(type);
if (superinterfaces != null && superinterfaces.length != 0)
{
addAllCheckingDuplicates(supers, superinterfaces);
for (int i = 0; i < superinterfaces.length; i++)
{
getAllSuperModules0(superinterfaces[i], supers);
}
}
IType superclass = this.classToSuperclass.get(type);
if (superclass != null)
{
supers.add(superclass);
getAllSupertypes0(superclass, supers);
}
}
/**
* @see ITypeHierarchy
*/
public IType[] getAllTypes()
{
IType[] classes = getAllClasses();
int classesLength = classes.length;
IType[] allInterfaces = getAllModules();
int interfacesLength = allInterfaces.length;
IType[] all = new IType[classesLength + interfacesLength];
System.arraycopy(classes, 0, all, 0, classesLength);
System.arraycopy(allInterfaces, 0, all, classesLength, interfacesLength);
return all;
}
/**
* @see ITypeHierarchy#getCachedFlags(IType)
*/
public int getCachedFlags(IType type)
{
Integer flagObject = (Integer) this.typeFlags.get(type);
if (flagObject != null)
{
return flagObject.intValue();
}
return -1;
}
/**
* @see ITypeHierarchy
*/
public IType[] getExtendingModules(IType type)
{
if (!this.isModule(type))
return NO_TYPE;
return getExtendingModules0(type);
}
/**
* Assumes that the type is an module
*
* @see #getExtendingModules
*/
private IType[] getExtendingModules0(IType extendedInterface)
{
ArrayList<IType> interfaceList = new ArrayList<IType>();
for (IType type : this.typeToSuperModules.keySet())
{
if (!this.isModule(type))
{
continue;
}
IType[] superInterfaces = this.typeToSuperModules.get(type);
if (superInterfaces != null)
{
for (IType superInterface : superInterfaces)
{
if (superInterface.equals(extendedInterface))
{
interfaceList.add(type);
}
}
}
}
IType[] extendingInterfaces = new IType[interfaceList.size()];
interfaceList.toArray(extendingInterfaces);
return extendingInterfaces;
}
/**
* @see ITypeHierarchy
*/
public IType[] getIncludingClasses(IType type)
{
if (!this.isModule(type))
{
return NO_TYPE;
}
return getIncludingClasses0(type);
}
/**
* Assumes that the type is an interface
*
* @see #getIncludingClasses
*/
private IType[] getIncludingClasses0(IType interfce)
{
ArrayList<IType> iMenters = new ArrayList<IType>();
for (IType type : this.typeToSuperModules.keySet())
{
if (this.isModule(type))
{
continue;
}
IType[] types = this.typeToSuperModules.get(type);
for (IType iFace : types)
{
if (iFace.equals(interfce))
{
iMenters.add(type);
}
}
}
IType[] implementers = new IType[iMenters.size()];
iMenters.toArray(implementers);
return implementers;
}
/**
* @see ITypeHierarchy
*/
public IType[] getRootClasses()
{
return this.rootClasses.elements();
}
/**
* @see ITypeHierarchy
*/
public IType[] getRootModules()
{
IType[] allInterfaces = getAllModules();
IType[] roots = new IType[allInterfaces.length];
int rootNumber = 0;
for (int i = 0; i < allInterfaces.length; i++)
{
IType[] superInterfaces = getSuperModules(allInterfaces[i]);
if (superInterfaces == null || superInterfaces.length == 0)
{
roots[rootNumber++] = allInterfaces[i];
}
}
IType[] result = new IType[rootNumber];
if (result.length > 0)
{
System.arraycopy(roots, 0, result, 0, rootNumber);
}
return result;
}
/**
* @see ITypeHierarchy
*/
public IType[] getSubclasses(IType type)
{
if (this.isModule(type))
{
return NO_TYPE;
}
TypeVector vector = (TypeVector) this.typeToSubtypes.get(type);
if (vector == null)
return NO_TYPE;
else
return vector.elements();
}
/**
* @see ITypeHierarchy
*/
public IType[] getSubtypes(IType type)
{
return getSubtypesForType(type);
}
/**
* Returns an array of subtypes for the given type - will never return null.
*/
private IType[] getSubtypesForType(IType type)
{
TypeVector vector = (TypeVector) this.typeToSubtypes.get(type);
if (vector == null)
return NO_TYPE;
else
return vector.elements();
}
/**
* @see ITypeHierarchy
*/
public IType getSuperclass(IType type)
{
if (this.isModule(type))
{
return null;
}
return (IType) this.classToSuperclass.get(type);
}
/**
* @see ITypeHierarchy
*/
public IType[] getSuperModules(IType type)
{
IType[] types = (IType[]) this.typeToSuperModules.get(type);
if (types == null)
{
return NO_TYPE;
}
return types;
}
/**
* @see ITypeHierarchy
*/
public IType[] getSupertypes(IType type)
{
IType superclass = getSuperclass(type);
if (superclass == null)
{
return getSuperModules(type);
}
else
{
TypeVector superTypes = new TypeVector(getSuperModules(type));
superTypes.add(superclass);
return superTypes.elements();
}
}
/**
* @see ITypeHierarchy
*/
public IType getType()
{
return this.focusType;
}
/**
* Adds the new elements to a new array that contains all of the elements of the old array. Returns the new array.
*/
protected IType[] growAndAddToArray(IType[] array, IType[] additions)
{
if (array == null || array.length == 0)
{
return additions;
}
IType[] old = array;
array = new IType[old.length + additions.length];
System.arraycopy(old, 0, array, 0, old.length);
System.arraycopy(additions, 0, array, old.length, additions.length);
return array;
}
/**
* Adds the new element to a new array that contains all of the elements of the old array. Returns the new array.
*/
protected IType[] growAndAddToArray(IType[] array, IType addition)
{
if (array == null || array.length == 0)
{
return new IType[] { addition };
}
IType[] old = array;
array = new IType[old.length + 1];
System.arraycopy(old, 0, array, 0, old.length);
array[old.length] = addition;
return array;
}
/*
* Whether fine-grained deltas where collected and affects this hierarchy.
*/
public boolean hasFineGrainChanges()
{
ChangeCollector collector = this.changeCollector;
return collector != null && collector.needsRefresh();
}
/**
* Returns whether one of the subtypes in this hierarchy has the given simple name or this type has the given simple
* name.
*/
private boolean hasSubtypeNamed(String simpleName)
{
if (this.focusType != null && this.focusType.getElementName().equals(simpleName))
{
return true;
}
IType[] types = this.focusType == null ? getAllTypes() : getAllSubtypes(this.focusType);
for (int i = 0, length = types.length; i < length; i++)
{
if (types[i].getElementName().equals(simpleName))
{
return true;
}
}
return false;
}
/**
* Returns whether one of the types in this hierarchy has the given simple name.
*/
private boolean hasTypeNamed(String simpleName)
{
IType[] types = this.getAllTypes();
for (int i = 0, length = types.length; i < length; i++)
{
if (types[i].getElementName().equals(simpleName))
{
return true;
}
}
return false;
}
/**
* Returns whether the simple name of the given type or one of its supertypes is the simple name of one of the types
* in this hierarchy.
*/
boolean includesTypeOrSupertype(IType type)
{
try
{
// check type
if (hasTypeNamed(type.getElementName()))
return true;
// check superclass
String superclassName = type.getSuperclassName();
if (superclassName != null)
{
int lastSeparator = superclassName.lastIndexOf('.');
String simpleName = superclassName.substring(lastSeparator + 1);
if (hasTypeNamed(simpleName))
return true;
}
// check superinterfaces
String[] superinterfaceNames = type.getIncludedModuleNames();
if (superinterfaceNames != null)
{
for (int i = 0, length = superinterfaceNames.length; i < length; i++)
{
String superinterfaceName = superinterfaceNames[i];
int lastSeparator = superinterfaceName.lastIndexOf('.');
String simpleName = superinterfaceName.substring(lastSeparator + 1);
if (hasTypeNamed(simpleName))
return true;
}
}
}
catch (RubyModelException e)
{
// ignore
}
return false;
}
/**
* Initializes this hierarchy's internal tables with the given size.
*/
protected void initialize(int size)
{
if (size < 10)
{
size = 10;
}
int smallSize = (size / 2);
this.classToSuperclass = new HashMap<IType, IType>(size);
this.modules = new ArrayList<IType>(smallSize);
this.missingTypes = new ArrayList<String>(smallSize);
this.rootClasses = new TypeVector();
this.typeToSubtypes = new HashMap<IType, TypeVector>(smallSize);
this.typeToSuperModules = new HashMap<IType, IType[]>(smallSize);
this.typeFlags = new HashMap<IType, Integer>(smallSize);
this.projectRegion = new Region();
this.packageRegion = new Region();
this.files = new HashMap<IOpenable, ArrayList<IType>>(5);
}
/**
* Returns true if the given delta could change this type hierarchy
*/
public synchronized boolean isAffected(IRubyElementDelta delta)
{
IRubyElement element = delta.getElement();
switch (element.getElementType())
{
case IRubyElement.RUBY_MODEL:
return isAffectedByRubyModel(delta, element);
case IRubyElement.RUBY_PROJECT:
return isAffectedByRubyProject(delta, element);
case IRubyElement.SOURCE_FOLDER_ROOT:
return isAffectedBySourceFolderRoot(delta, element);
case IRubyElement.SOURCE_FOLDER:
return isAffectedBySourceFolder(delta, (SourceFolder) element);
// case IRubyElement.CLASS_FILE:
case IRubyElement.SCRIPT:
return isAffectedByOpenable(delta, element);
}
return false;
}
/**
* Returns true if any of the children of a project, package fragment root, or package fragment have changed in a
* way that effects this type hierarchy.
*/
private boolean isAffectedByChildren(IRubyElementDelta delta)
{
if ((delta.getFlags() & IRubyElementDelta.F_CHILDREN) > 0)
{
IRubyElementDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++)
{
if (isAffected(children[i]))
{
return true;
}
}
}
return false;
}
/**
* Returns true if the given java model delta could affect this type hierarchy
*/
private boolean isAffectedByRubyModel(IRubyElementDelta delta, IRubyElement element)
{
switch (delta.getKind())
{
case IRubyElementDelta.ADDED:
case IRubyElementDelta.REMOVED:
return element.equals(this.rubyProject().getRubyModel());
case IRubyElementDelta.CHANGED:
return isAffectedByChildren(delta);
}
return false;
}
/**
* Returns true if the given java project delta could affect this type hierarchy
*/
private boolean isAffectedByRubyProject(IRubyElementDelta delta, IRubyElement element)
{
int kind = delta.getKind();
int flags = delta.getFlags();
if ((flags & IRubyElementDelta.F_OPENED) != 0)
{
kind = IRubyElementDelta.ADDED; // affected in the same way
}
if ((flags & IRubyElementDelta.F_CLOSED) != 0)
{
kind = IRubyElementDelta.REMOVED; // affected in the same way
}
switch (kind)
{
case IRubyElementDelta.ADDED:
try
{
// if the added project is on the classpath, then the hierarchy has changed
ILoadpathEntry[] classpath = ((RubyProject) this.rubyProject()).getExpandedLoadpath(true);
for (int i = 0; i < classpath.length; i++)
{
if (classpath[i].getEntryKind() == ILoadpathEntry.CPE_PROJECT
&& classpath[i].getPath().equals(element.getPath()))
{
return true;
}
}
if (this.focusType != null)
{
// if the hierarchy's project is on the added project classpath, then the hierarchy has changed
classpath = ((RubyProject) element).getExpandedLoadpath(true);
IPath hierarchyProject = rubyProject().getPath();
for (int i = 0; i < classpath.length; i++)
{
if (classpath[i].getEntryKind() == ILoadpathEntry.CPE_PROJECT
&& classpath[i].getPath().equals(hierarchyProject))
{
return true;
}
}
}
return false;
}
catch (RubyModelException e)
{
return false;
}
case IRubyElementDelta.REMOVED:
// removed project - if it contains packages we are interested in
// then the type hierarchy has changed
IRubyElement[] pkgs = this.packageRegion.getElements();
for (int i = 0; i < pkgs.length; i++)
{
IRubyProject javaProject = pkgs[i].getRubyProject();
if (javaProject != null && javaProject.equals(element))
{
return true;
}
}
return false;
case IRubyElementDelta.CHANGED:
return isAffectedByChildren(delta);
}
return false;
}
/**
* Returns true if the given package fragment delta could affect this type hierarchy
*/
private boolean isAffectedBySourceFolder(IRubyElementDelta delta, SourceFolder element)
{
switch (delta.getKind())
{
case IRubyElementDelta.ADDED:
// if the package fragment is in the projects being considered, this could
// introduce new types, changing the hierarchy
return this.projectRegion.contains(element);
case IRubyElementDelta.REMOVED:
// is a change if the package fragment contains types in this hierarchy
return packageRegionContainsSameSourceFolder(element);
case IRubyElementDelta.CHANGED:
// look at the files in the package fragment
return isAffectedByChildren(delta);
}
return false;
}
/**
* Returns true if the given package fragment root delta could affect this type hierarchy
*/
private boolean isAffectedBySourceFolderRoot(IRubyElementDelta delta, IRubyElement element)
{
switch (delta.getKind())
{
case IRubyElementDelta.ADDED:
return this.projectRegion.contains(element);
case IRubyElementDelta.REMOVED:
case IRubyElementDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IRubyElementDelta.F_ADDED_TO_CLASSPATH) > 0)
{
// check if the root is in the classpath of one of the projects of this hierarchy
if (this.projectRegion != null)
{
ISourceFolderRoot root = (ISourceFolderRoot) element;
IPath rootPath = root.getPath();
IRubyElement[] elements = this.projectRegion.getElements();
for (int i = 0; i < elements.length; i++)
{
RubyProject javaProject = (RubyProject) elements[i];
try
{
ILoadpathEntry[] classpath = javaProject.getResolvedLoadpath(
true/* ignoreUnresolvedEntry */, false/*
* don't generateMarkerOnError
*/, false/*
* don't returnResolutionInProgress
*/);
for (int j = 0; j < classpath.length; j++)
{
ILoadpathEntry entry = classpath[j];
if (entry.getPath().equals(rootPath))
{
return true;
}
}
}
catch (RubyModelException e)
{
// igmore this project
}
}
}
}
if ((flags & IRubyElementDelta.F_REMOVED_FROM_CLASSPATH) > 0
|| (flags & IRubyElementDelta.F_CONTENT) > 0)
{
// 1. removed from classpath - if it contains packages we are interested in
// the the type hierarchy has changed
// 2. content of a jar changed - if it contains packages we are interested in
// the the type hierarchy has changed
IRubyElement[] pkgs = this.packageRegion.getElements();
for (int i = 0; i < pkgs.length; i++)
{
if (pkgs[i].getParent().equals(element))
{
return true;
}
}
return false;
}
}
return isAffectedByChildren(delta);
}
/**
* Returns true if the given type delta (a compilation unit delta or a class file delta) could affect this type
* hierarchy.
*/
protected boolean isAffectedByOpenable(IRubyElementDelta delta, IRubyElement element)
{
if (element instanceof RubyScript)
{
RubyScript cu = (RubyScript) element;
ChangeCollector collector = this.changeCollector;
if (collector == null)
{
collector = new ChangeCollector(this);
}
try
{
collector.addChange(cu, delta);
}
catch (RubyModelException e)
{
if (DEBUG)
e.printStackTrace();
}
if (cu.isWorkingCopy())
{
// changes to working copies are batched
this.changeCollector = collector;
return false;
}
else
{
return collector.needsRefresh();
}
}
return false;
}
private boolean isModule(IType type)
{
return type.isModule();
}
/**
* Returns the ruby project this hierarchy was created in.
*/
public IRubyProject rubyProject()
{
return this.focusType.getRubyProject();
}
protected static byte[] readUntil(InputStream input, byte separator) throws RubyModelException, IOException
{
return readUntil(input, separator, 0);
}
protected static byte[] readUntil(InputStream input, byte separator, int offset) throws IOException,
RubyModelException
{
int length = 0;
byte[] bytes = new byte[SIZE];
byte b;
while ((b = (byte) input.read()) != separator && b != -1)
{
if (bytes.length == length)
{
System.arraycopy(bytes, 0, bytes = new byte[length * 2], 0, length);
}
bytes[length++] = b;
}
if (b == -1)
{
throw new RubyModelException(new RubyModelStatus(IStatus.ERROR));
}
System.arraycopy(bytes, 0, bytes = new byte[length + offset], offset, length);
return bytes;
}
public static ITypeHierarchy load(IType type, InputStream input, WorkingCopyOwner owner) throws RubyModelException
{
try
{
TypeHierarchy typeHierarchy = new TypeHierarchy();
typeHierarchy.initialize(1);
IType[] types = new IType[SIZE];
int typeCount = 0;
byte version = (byte) input.read();
if (version != VERSION)
{
throw new RubyModelException(new RubyModelStatus(IStatus.ERROR));
}
byte generalInfo = (byte) input.read();
if ((generalInfo & COMPUTE_SUBTYPES) != 0)
{
typeHierarchy.computeSubtypes = true;
}
byte b;
byte[] bytes;
// read project
bytes = readUntil(input, SEPARATOR1);
if (bytes.length > 0)
{
typeHierarchy.project = (IRubyProject) RubyCore.create(new String(bytes));
typeHierarchy.scope = SearchEngine.createRubySearchScope(new IRubyElement[] { typeHierarchy.project });
}
else
{
typeHierarchy.project = null;
typeHierarchy.scope = SearchEngine.createWorkspaceScope();
}
// read missing type
{
bytes = readUntil(input, SEPARATOR1);
byte[] missing;
int j = 0;
int length = bytes.length;
for (int i = 0; i < length; i++)
{
b = bytes[i];
if (b == SEPARATOR2)
{
missing = new byte[i - j];
System.arraycopy(bytes, j, missing, 0, i - j);
typeHierarchy.missingTypes.add(new String(missing));
j = i + 1;
}
}
System.arraycopy(bytes, j, missing = new byte[length - j], 0, length - j);
typeHierarchy.missingTypes.add(new String(missing));
}
// read types
while ((b = (byte) input.read()) != SEPARATOR1 && b != -1)
{
bytes = readUntil(input, SEPARATOR4, 1);
bytes[0] = b;
IType element = (IType) RubyCore.create(new String(bytes), owner);
if (types.length == typeCount)
{
System.arraycopy(types, 0, types = new IType[typeCount * 2], 0, typeCount);
}
types[typeCount++] = element;
// read flags
bytes = readUntil(input, SEPARATOR4);
Integer flags = bytesToFlags(bytes);
if (flags != null)
{
typeHierarchy.cacheFlags(element, flags.intValue());
}
// read info
byte info = (byte) input.read();
if ((info & INTERFACE) != 0)
{
typeHierarchy.addModule(element);
}
if ((info & COMPUTED_FOR) != 0)
{
if (!element.equals(type))
{
throw new RubyModelException(new RubyModelStatus(IStatus.ERROR));
}
typeHierarchy.focusType = element;
}
if ((info & ROOT) != 0)
{
typeHierarchy.addRootClass(element);
}
}
// read super class
while ((b = (byte) input.read()) != SEPARATOR1 && b != -1)
{
bytes = readUntil(input, SEPARATOR3, 1);
bytes[0] = b;
int subClass = new Integer(new String(bytes)).intValue();
// read super type
bytes = readUntil(input, SEPARATOR1);
int superClass = new Integer(new String(bytes)).intValue();
typeHierarchy.cacheSuperclass(types[subClass], types[superClass]);
}
// read super interface
while ((b = (byte) input.read()) != SEPARATOR1 && b != -1)
{
bytes = readUntil(input, SEPARATOR3, 1);
bytes[0] = b;
int subClass = new Integer(new String(bytes)).intValue();
// read super interface
bytes = readUntil(input, SEPARATOR1);
IType[] superInterfaces = new IType[(bytes.length / 2) + 1];
int interfaceCount = 0;
int j = 0;
byte[] b2;
for (int i = 0; i < bytes.length; i++)
{
if (bytes[i] == SEPARATOR2)
{
b2 = new byte[i - j];
System.arraycopy(bytes, j, b2, 0, i - j);
j = i + 1;
superInterfaces[interfaceCount++] = types[new Integer(new String(b2)).intValue()];
}
}
b2 = new byte[bytes.length - j];
System.arraycopy(bytes, j, b2, 0, bytes.length - j);
superInterfaces[interfaceCount++] = types[new Integer(new String(b2)).intValue()];
System.arraycopy(superInterfaces, 0, superInterfaces = new IType[interfaceCount], 0, interfaceCount);
typeHierarchy.cacheSuperModules(types[subClass], superInterfaces);
}
if (b == -1)
{
throw new RubyModelException(new RubyModelStatus(IStatus.ERROR));
}
return typeHierarchy;
}
catch (IOException e)
{
throw new RubyModelException(e, IRubyModelStatusConstants.IO_EXCEPTION);
}
}
/**
* Returns <code>true</code> if an equivalent package fragment is included in the package region. Package fragments
* are equivalent if they both have the same name.
*/
protected boolean packageRegionContainsSameSourceFolder(SourceFolder element)
{
IRubyElement[] pkgs = this.packageRegion.getElements();
for (int i = 0; i < pkgs.length; i++)
{
SourceFolder pkg = (SourceFolder) pkgs[i];
if (Util.equalArraysOrNull(pkg.names, element.names))
return true;
}
return false;
}
/**
* @see ITypeHierarchy TODO (jerome) should use a PerThreadObject to build the hierarchy instead of synchronizing
* (see also isAffected(IRubyElementDelta))
*/
public synchronized void refresh(IProgressMonitor monitor) throws RubyModelException
{
try
{
this.progressMonitor = monitor;
if (monitor != null)
{
if (this.focusType != null)
{
monitor.beginTask(Messages.bind(Messages.hierarchy_creatingOnType, this.focusType
.getFullyQualifiedName()), 100);
}
else
{
monitor.beginTask(Messages.hierarchy_creating, 100);
}
}
long start = -1;
if (DEBUG)
{
start = System.currentTimeMillis();
if (this.computeSubtypes)
{
System.out.println("CREATING TYPE HIERARCHY [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
System.out.println("CREATING SUPER TYPE HIERARCHY [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (this.focusType != null)
{
System.out.println(" on type " + ((RubyElement) this.focusType).toStringWithAncestors()); //$NON-NLS-1$
}
}
compute();
initializeRegions();
this.needsRefresh = false;
this.changeCollector = null;
if (DEBUG)
{
if (this.computeSubtypes)
{
System.out.println("CREATED TYPE HIERARCHY in " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
System.out
.println("CREATED SUPER TYPE HIERARCHY in " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
System.out.println(this.toString());
}
}
catch (RubyModelException e)
{
throw e;
}
catch (CoreException e)
{
throw new RubyModelException(e);
}
finally
{
if (monitor != null)
{
monitor.done();
}
this.progressMonitor = null;
}
}
/**
* @see ITypeHierarchy
*/
public synchronized void removeTypeHierarchyChangedListener(ITypeHierarchyChangedListener listener)
{
ArrayList<ITypeHierarchyChangedListener> listeners = this.changeListeners;
if (listeners == null)
{
return;
}
listeners.remove(listener);
// deregister from RubyCore on last listener removed
if (listeners.isEmpty())
{
RubyCore.removeElementChangedListener(this);
}
}
/**
* @see ITypeHierarchy
*/
public void store(OutputStream output, IProgressMonitor monitor) throws RubyModelException
{
try
{
// compute types in hierarchy
Hashtable<IType, Integer> hashtable = new Hashtable<IType, Integer>();
Hashtable<Integer, IType> hashtable2 = new Hashtable<Integer, IType>();
int count = 0;
if (this.focusType != null)
{
Integer index = new Integer(count++);
hashtable.put(this.focusType, index);
hashtable2.put(index, this.focusType);
}
IType[] types = (IType[]) this.classToSuperclass.keySet().toArray();
for (IType t : types)
{
if (hashtable.get(t) == null)
{
Integer index = new Integer(count++);
hashtable.put(t, index);
hashtable2.put(index, t);
}
IType superClass = this.classToSuperclass.get(t);
if (superClass != null && hashtable.get(superClass) == null)
{
Integer index = new Integer(count++);
hashtable.put(superClass, index);
hashtable2.put(index, superClass);
}
}
types = (IType[]) this.typeToSuperModules.keySet().toArray();
for (IType t : types)
{
if (hashtable.get(t) == null)
{
Integer index = new Integer(count++);
hashtable.put(t, index);
hashtable2.put(index, t);
}
IType[] sp = this.typeToSuperModules.get(t);
if (sp != null)
{
for (IType superInterface : sp)
{
if (superInterface != null && hashtable.get(superInterface) == null)
{
Integer index = new Integer(count++);
hashtable.put(superInterface, index);
hashtable2.put(index, superInterface);
}
}
}
}
// save version of the hierarchy format
output.write(VERSION);
// save general info
byte generalInfo = 0;
if (this.computeSubtypes)
{
generalInfo |= COMPUTE_SUBTYPES;
}
output.write(generalInfo);
// save project
if (this.project != null)
{
output.write(this.project.getHandleIdentifier().getBytes());
}
output.write(SEPARATOR1);
// save missing types
for (int i = 0; i < this.missingTypes.size(); i++)
{
if (i != 0)
{
output.write(SEPARATOR2);
}
output.write(((String) this.missingTypes.get(i)).getBytes());
}
output.write(SEPARATOR1);
// save types
for (int i = 0; i < count; i++)
{
IType t = hashtable2.get(new Integer(i));
// n bytes
output.write(t.getHandleIdentifier().getBytes());
output.write(SEPARATOR4);
output.write(flagsToBytes(this.typeFlags.get(t)));
output.write(SEPARATOR4);
byte info = CLASS;
if (this.focusType != null && this.focusType.equals(t))
{
info |= COMPUTED_FOR;
}
if (this.modules.contains(t))
{
info |= INTERFACE;
}
if (this.rootClasses.contains(t))
{
info |= ROOT;
}
output.write(info);
}
output.write(SEPARATOR1);
// save superclasses
types = (IType[]) this.classToSuperclass.keySet().toArray();
for (int i = 0; i < types.length; i++)
{
IRubyElement key = types[i];
IRubyElement value = this.classToSuperclass.get(key);
output.write((hashtable.get(key)).toString().getBytes());
output.write('>');
output.write((hashtable.get(value)).toString().getBytes());
output.write(SEPARATOR1);
}
output.write(SEPARATOR1);
// save superinterfaces
types = (IType[]) this.typeToSuperModules.keySet().toArray();
for (int i = 0; i < types.length; i++)
{
IRubyElement key = types[i];
IRubyElement[] values = this.typeToSuperModules.get(key);
if (values.length > 0)
{
output.write((hashtable.get(key)).toString().getBytes());
output.write(SEPARATOR3);
for (int j = 0; j < values.length; j++)
{
IRubyElement value = values[j];
if (j != 0)
output.write(SEPARATOR2);
output.write((hashtable.get(value)).toString().getBytes());
}
output.write(SEPARATOR1);
}
}
output.write(SEPARATOR1);
}
catch (IOException e)
{
throw new RubyModelException(e, IRubyModelStatusConstants.IO_EXCEPTION);
}
}
/**
* Returns whether the simple name of a supertype of the given type is the simple name of one of the subtypes in
* this hierarchy or the simple name of this type.
*/
boolean subtypesIncludeSupertypeOf(IType type)
{
// look for superclass
String superclassName = null;
try
{
superclassName = type.getSuperclassName();
}
catch (RubyModelException e)
{
if (DEBUG)
{
e.printStackTrace();
}
return false;
}
if (superclassName == null)
{
superclassName = "Object"; //$NON-NLS-1$
}
int dot = -1;
String simpleSuper = (dot = superclassName.lastIndexOf('.')) > -1 ? superclassName.substring(dot + 1)
: superclassName;
if (hasSubtypeNamed(simpleSuper))
{
return true;
}
// look for super interfaces
String[] interfaceNames = null;
try
{
interfaceNames = type.getIncludedModuleNames();
}
catch (RubyModelException e)
{
if (DEBUG)
e.printStackTrace();
return false;
}
for (int i = 0, length = interfaceNames.length; i < length; i++)
{
dot = -1;
String interfaceName = interfaceNames[i];
String simpleInterface = (dot = interfaceName.lastIndexOf('.')) > -1 ? interfaceName.substring(dot)
: interfaceName;
if (hasSubtypeNamed(simpleInterface))
{
return true;
}
}
return false;
}
/**
* @see ITypeHierarchy
*/
public String toString()
{
StringBuffer buffer = new StringBuffer();
buffer.append("Focus: "); //$NON-NLS-1$
buffer.append(this.focusType == null ? "<NONE>" : ((RubyElement) this.focusType)
.toStringWithAncestors(false/*
* don't show key
*/)); //$NON-NLS-1$
buffer.append("\n"); //$NON-NLS-1$
if (exists())
{
if (this.focusType != null)
{
buffer.append("Super types:\n"); //$NON-NLS-1$
toString(buffer, this.focusType, 1, true);
buffer.append("Sub types:\n"); //$NON-NLS-1$
toString(buffer, this.focusType, 1, false);
}
else
{
buffer.append("Sub types of root classes:\n"); //$NON-NLS-1$
IRubyElement[] roots = Util.sortCopy(getRootClasses());
for (int i = 0; i < roots.length; i++)
{
toString(buffer, (IType) roots[i], 1, false);
}
}
if (this.rootClasses.size > 1)
{
buffer.append("Root classes:\n"); //$NON-NLS-1$
IRubyElement[] roots = Util.sortCopy(getRootClasses());
for (int i = 0, length = roots.length; i < length; i++)
{
toString(buffer, (IType) roots[i], 1, false);
}
}
else if (this.rootClasses.size == 0)
{
// see http://bugs.eclipse.org/bugs/show_bug.cgi?id=24691
buffer.append("No root classes"); //$NON-NLS-1$
}
}
else
{
buffer.append("(Hierarchy became stale)"); //$NON-NLS-1$
}
return buffer.toString();
}
/**
* Append a String to the given buffer representing the hierarchy for the type, beginning with the specified
* indentation level. If ascendant, shows the super types, otherwise show the sub types.
*/
private void toString(StringBuffer buffer, IType type, int indent, boolean ascendant)
{
IType[] types = ascendant ? getSupertypes(type) : getSubtypes(type);
IRubyElement[] sortedTypes = Util.sortCopy(types);
for (int i = 0; i < sortedTypes.length; i++)
{
for (int j = 0; j < indent; j++)
{
buffer.append(" "); //$NON-NLS-1$
}
RubyElement element = (RubyElement) sortedTypes[i];
buffer.append(element.toStringWithAncestors(false/* don't show key */));
buffer.append('\n');
toString(buffer, types[i], indent + 1, ascendant);
}
}
/**
* Returns whether one of the types in this hierarchy has a supertype whose simple name is the given simple name.
*/
boolean hasSupertype(String simpleName)
{
for (IType superType : this.classToSuperclass.values())
{
if (superType.getElementName().equals(simpleName))
{
return true;
}
}
return false;
}
/**
* @see IProgressMonitor
*/
protected void worked(int work)
{
if (this.progressMonitor != null)
{
this.progressMonitor.worked(work);
checkCanceled();
}
}
}