/**
* Aptana Studio
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.php.internal.model.impl;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import com.aptana.editor.php.core.model.ISourceModule;
import com.aptana.editor.php.core.model.IType;
import com.aptana.editor.php.core.model.ITypeHierarchyChangedListener;
import com.aptana.editor.php.indexer.IElementEntry;
import com.aptana.editor.php.indexer.IElementsIndex;
import com.aptana.editor.php.indexer.IModuleIndexListener;
import com.aptana.editor.php.indexer.IPHPIndexConstants;
import com.aptana.editor.php.indexer.PHPGlobalIndexer;
import com.aptana.editor.php.internal.core.builder.IDirectory;
import com.aptana.editor.php.internal.core.builder.IModule;
import com.aptana.editor.php.internal.model.ITypeHierarchy;
import com.aptana.editor.php.internal.model.utils.ModelUtils;
import com.aptana.editor.php.internal.model.utils.TypeHierarchyUtils;
/**
* Global type hierarchy based on php index. This type hierarchy is not based on a single type, but instead is dynamic
* and valid for the whole workspace.
*
* @author Denis Denisenko
*/
public class IndexBasedTypeHierarchy implements ITypeHierarchy
{
/**
* Listeners.
*/
private Set<ITypeHierarchyChangedListener> listeners = new HashSet<ITypeHierarchyChangedListener>();
/**
* Module index listener.
*/
private IModuleIndexListener moduleIndexListener = null;
/**
* Base type.
*/
private IType baseType;
/**
* {@inheritDoc}
*/
public void addTypeHierarchyChangedListener(ITypeHierarchyChangedListener listener)
{
synchronized (listeners)
{
listeners.add(listener);
ensureModelListenerCreated();
}
}
/**
* {@inheritDoc}
*/
public boolean contains(IType type)
{
return true;
}
/**
* {@inheritDoc}
*/
public boolean exists()
{
return true;
}
/**
* {@inheritDoc}
*/
public List<IType> getAllClasses()
{
IElementsIndex index = PHPGlobalIndexer.getInstance().getIndex();
List<IElementEntry> entries = index.getEntriesStartingWith(IPHPIndexConstants.CLASS_CATEGORY, ""); //$NON-NLS-1$
return ModelUtils.convertTypes(entries);
}
/**
* {@inheritDoc}
*/
public List<IType> getAllSubtypes(IType type)
{
// TODO - Shalom: add namespace and aliases support into IType
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classDescendants = TypeHierarchyUtils.getClassDescendants(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex(), null, null);
return ModelUtils.convertTypes(classDescendants);
}
/**
* {@inheritDoc}
*/
public List<IType> getAllSuperclasses(IType type)
{
// TODO - Shalom: add namespace and aliases support into IType
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classAncestors = TypeHierarchyUtils.getClassAncestors(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex(), null, null);
return ModelUtils.convertClasses(classAncestors);
}
/**
* {@inheritDoc}
*/
public List<IType> getAllSupertypes(IType type)
{
// TODO - Shalom: add namespace and aliases support into IType
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classAncestors = TypeHierarchyUtils.getClassAncestors(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex(), null, null);
return ModelUtils.convertTypes(classAncestors);
}
/**
* {@inheritDoc}
*/
public List<IType> getAllTypes()
{
IElementsIndex index = PHPGlobalIndexer.getInstance().getIndex();
List<IElementEntry> entries = index.getEntriesStartingWith(IPHPIndexConstants.CLASS_CATEGORY, ""); //$NON-NLS-1$
return ModelUtils.convertTypes(entries);
}
/**
* {@inheritDoc}
*/
public int getCachedFlags(IType type)
{
return type.getFlags();
}
/**
* {@inheritDoc}
*/
public List<IType> getRootClasses()
{
if (baseType == null)
{
return Collections.emptyList();
}
Set<IType> result = new HashSet<IType>();
addRootClassesRecursively(baseType, result);
List<IType> toReturn = new ArrayList<IType>(result.size());
toReturn.addAll(result);
return toReturn;
}
/**
* {@inheritDoc}
*/
public List<IType> getSubclasses(IType type)
{
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classDescendants = TypeHierarchyUtils.getDirectClassDescendants(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex());
return ModelUtils.convertClasses(classDescendants);
}
/**
* {@inheritDoc}
*/
public List<IType> getSubtypes(IType type)
{
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classDescendants = TypeHierarchyUtils.getDirectClassDescendants(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex());
return ModelUtils.convertTypes(classDescendants);
}
/**
* {@inheritDoc}
*/
public List<IType> getSuperclass(IType type)
{
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classAncestors = TypeHierarchyUtils.getDirectClassAncestors(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex());
return ModelUtils.convertClasses(classAncestors);
}
/**
* {@inheritDoc}
*/
public List<IType> getSupertypes(IType type)
{
IElementEntry typeEntry = getTypeEntry(type);
List<IElementEntry> classAncestors = TypeHierarchyUtils.getDirectClassAncestors(typeEntry.getModule(),
typeEntry.getEntryPath(), PHPGlobalIndexer.getInstance().getIndex());
return ModelUtils.convertTypes(classAncestors);
}
/**
* {@inheritDoc}
*/
public IType getType()
{
return baseType;
}
/**
* {@inheritDoc}
*/
public void refresh(IProgressMonitor monitor)
{
monitor.done();
}
/**
* {@inheritDoc}
*/
public void removeTypeHierarchyChangedListener(ITypeHierarchyChangedListener listener)
{
synchronized (listeners)
{
listeners.remove(listener);
removeModelListenerIfNeeded();
}
}
/**
* {@inheritDoc}
*/
public void store(OutputStream outputStream, IProgressMonitor monitor)
{
monitor.done();
}
/**
* Sets base type.
*
* @param type
* - type.
*/
public void setType(IType type)
{
this.baseType = type;
}
/**
* Gets type entry.
*
* @param type
* - type.
* @return type entry.
*/
private IElementEntry getTypeEntry(IType type)
{
return ((EntryBasedType) type).getEntry();
}
/**
* Notifies hierarchy changed.
*
* @param changed
* - changed types.
*/
private void notifyChanged(Set<IType> changed)
{
Set<ITypeHierarchyChangedListener> listenersCopy = new HashSet<ITypeHierarchyChangedListener>();
synchronized (listeners)
{
listenersCopy.addAll(listeners);
}
for (ITypeHierarchyChangedListener listener : listenersCopy)
{
listener.typeHierarchyChanged(changed);
}
}
/**
* Ensures model listener is created.
*/
private void ensureModelListenerCreated()
{
if (moduleIndexListener == null)
{
moduleIndexListener = new IModuleIndexListener()
{
public void afterIndexChange(List<IModule> added, List<IModule> changed,
List<IDirectory> addedDirectories)
{
Set<IType> changedTypes = new HashSet<IType>();
for (IModule module : changed)
{
ISourceModule sourceModule = ModelUtils.convertModule(module);
if (sourceModule != null)
{
changedTypes.addAll(sourceModule.getTopLevelTypes());
}
}
for (IModule module : added)
{
ISourceModule sourceModule = ModelUtils.convertModule(module);
if (sourceModule != null)
{
changedTypes.addAll(sourceModule.getTopLevelTypes());
}
}
notifyChanged(changedTypes);
}
public void beforeIndexChange(List<IModule> changed, List<IModule> removed,
List<IDirectory> removedDirectories)
{
Set<IType> changedTypes = new HashSet<IType>();
for (IModule module : changed)
{
ISourceModule sourceModule = ModelUtils.convertModule(module);
if (sourceModule != null)
{
changedTypes.addAll(sourceModule.getTopLevelTypes());
}
}
for (IModule module : removed)
{
ISourceModule sourceModule = ModelUtils.convertModule(module);
if (sourceModule != null)
{
changedTypes.addAll(sourceModule.getTopLevelTypes());
}
}
notifyChanged(changedTypes);
}
};
PHPGlobalIndexer.getInstance().addListener(moduleIndexListener);
}
}
/**
* Removes model listener if needed.
*/
private void removeModelListenerIfNeeded()
{
if (listeners.size() == 0)
{
PHPGlobalIndexer.getInstance().removeListener(moduleIndexListener);
moduleIndexListener = null;
}
}
/**
* Adds root classes and interfaces recursivelly.
*
* @param type
* - type to check.
* @param result
* - result.
*/
private void addRootClassesRecursively(IType type, Set<IType> result)
{
List<IType> superTypes = type.getSuperTypes();
// if no ancestors found, adding this root type to the result list
if (superTypes == null || superTypes.size() == 0)
{
result.add(type);
return;
}
for (IType superType : superTypes)
{
addRootClassesRecursively(superType, result);
}
}
}