package com.redhat.ceylon.eclipse.code.search; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.asSourceModule; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getPackage; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getUnit; import static org.eclipse.jdt.core.IJavaElement.PACKAGE_FRAGMENT; import static org.eclipse.jdt.core.IJavaElement.PACKAGE_FRAGMENT_ROOT; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.search.ui.text.AbstractTextSearchResult; import com.redhat.ceylon.compiler.typechecker.io.VirtualFile; import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder; import com.redhat.ceylon.ide.common.model.BaseIdeModule; import com.redhat.ceylon.ide.common.model.CeylonBinaryUnit; import com.redhat.ceylon.ide.common.model.IJavaModelAware; import com.redhat.ceylon.ide.common.model.IResourceAware; import com.redhat.ceylon.ide.common.model.IdeUnit; import com.redhat.ceylon.ide.common.model.JavaClassFile; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Unit; class CeylonSearchResultTreeContentProvider implements CeylonStructuredContentProvider, ITreeContentProvider { static final int LEVEL_FILE = 0; static final int LEVEL_PACKAGE = 1; static final int LEVEL_MODULE = 2; static final int LEVEL_FOLDER = 3; static final int LEVEL_PROJECT = 4; private static final Object[] EMPTY_ARR = new Object[0]; private final TreeViewer viewer; private CeylonSearchResult result; private CeylonSearchResultPage page; private Map<Object, Set<Object>> childrenMap; private int level; private boolean showCategories; CeylonSearchResultTreeContentProvider(TreeViewer viewer, CeylonSearchResultPage page) { this.viewer = viewer; this.page = page; } public Object[] getElements(Object inputElement) { return getChildren(inputElement); } public void dispose() {} public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if (newInput instanceof AbstractTextSearchResult) { initialize((AbstractTextSearchResult) newInput); } } private synchronized void initialize( AbstractTextSearchResult result) { this.result = (CeylonSearchResult) result; childrenMap = new HashMap<Object, Set<Object>>(); if (result != null) { Object[] elements = result.getElements(); for (int i= 0; i<elements.length; i++) { Object element = elements[i]; if (page.getDisplayedMatchCount(element) > 0) { insert(null, null, element); } } } } public Object[] getChildren(Object parentElement) { Set<Object> children = childrenMap.get(parentElement); if (children == null) { return EMPTY_ARR; } int limit = page.getElementLimit().intValue(); if (limit!=-1 && limit<children.size()) { Object[] limitedArray = new Object[limit]; Iterator<Object> iterator = children.iterator(); for (int i=0; i<limit; i++) { limitedArray[i] = iterator.next(); } return limitedArray; } return children.toArray(); } public boolean hasChildren(Object element) { Set<Object> children = childrenMap.get(element); return children!=null && !children.isEmpty(); } public synchronized void elementsChanged( Object[] updatedElements) { if (result!=null) { Set<Object> removed = new HashSet<Object>(); Set<Object> updated = new HashSet<Object>(); Map<Object,Set<Object>> added = new HashMap<Object,Set<Object>>(); for (int i= 0; i<updatedElements.length; i++) { Object element = updatedElements[i]; if (page.getDisplayedMatchCount(element) > 0) { insert(added, updated, element); } else { remove(removed, updated, element); } } viewer.remove(removed.toArray()); for (Map.Entry<Object, Set<Object>> entry: added.entrySet()) { Object parent = entry.getKey(); Set<Object> children = added.get(parent); viewer.add(parent, children.toArray()); } for (Object element: updated) { viewer.refresh(element); } } } protected void insert( Map<Object, Set<Object>> toAdd, Set<Object> toUpdate, Object child) { Object parent = getParent(child); while (parent!=null) { if (insertChild(parent, child)) { if (toAdd!=null) { insertInto(parent, child, toAdd); } } else { if (toUpdate!=null) { toUpdate.add(parent); } return; } child = parent; parent = getParent(child); } if (insertChild(result, child)) { if (toAdd!=null) { insertInto(result, child, toAdd); } } } private boolean insertChild(Object parent, Object child) { return insertInto(parent, child, childrenMap); } private boolean insertInto(Object parent, Object child, Map<Object, Set<Object>> map) { Set<Object> children = map.get(parent); if (children==null) { children = new HashSet<Object>(); map.put(parent, children); } return children.add(child); } protected void remove( Set<Object> toRemove, Set<Object> toUpdate, Object element) { // precondition here: fResult.getMatchCount(child) <= 0 if (hasChildren(element)) { if (toUpdate != null) { toUpdate.add(element); } } else { if (page.getDisplayedMatchCount(element) == 0) { childrenMap.remove(element); Object parent = getParent(element); if (parent!=null) { if (removeFromSiblings(element, parent)) { remove(toRemove, toUpdate, parent); } } else { if (removeFromSiblings(element, result)) { if (toRemove!=null) { toRemove.add(element); } } } } else { if (toUpdate!=null) { toUpdate.add(element); } } } } /** * Tries to remove the given element from the list of stored siblings. * * @param element potential child * @param parent potential parent * @return returns true if it really was a remove (i.e. element was a child of parent). */ private boolean removeFromSiblings( Object element, Object parent) { Set<Object> siblings = childrenMap.get(parent); if (siblings!=null) { return siblings.remove(element); } else { return false; } } public Object getParent(Object child) { if (!showCategories) { return getParentInternal(child); } CeylonSearchMatch.Type category; if (child instanceof CeylonSearchMatch.Type) { return null; } else if (child instanceof CeylonElement) { CeylonElement match = (CeylonElement) child; category = match.getType(); } else if (child instanceof WithCategory) { WithCategory wc = (WithCategory) child; category = wc.getCategory(); child = wc.getItem(); } else { category = CeylonSearchMatch.Type.JAVA; } Object parent = getParentInternal(child); return parent == null ? category : new WithCategory(parent, category); } public Object getParentInternal(Object child) { if (child instanceof IProject) { return null; } if (child instanceof IPackageFragmentRoot) { if (level==LEVEL_FOLDER) { return null; } IPackageFragmentRoot sourceFolder = (IPackageFragmentRoot) child; return sourceFolder.getJavaProject() .getProject(); } if (child instanceof WithSourceFolder) { WithSourceFolder wsf = (WithSourceFolder) child; Object element = wsf.element; IPackageFragmentRoot sourceFolder = wsf.sourceFolder; if (element instanceof Unit) { if (level==LEVEL_FILE) { return null; } Unit unit = (Unit) element; Package pack = unit.getPackage(); return new WithSourceFolder(pack, sourceFolder); } if (element instanceof Package) { if (level==LEVEL_PACKAGE) { return null; } Package p = (Package) element; Module mod = p.getModule(); return new WithSourceFolder(mod, sourceFolder); } if (element instanceof Module) { if (level==LEVEL_MODULE) { return null; } return sourceFolder==null ? ArchiveMatches.INSTANCE : sourceFolder; } if (element instanceof IPackageFragment) { if (level==LEVEL_PACKAGE) { return null; } //let's see if it belongs to a Ceylon module IPackageFragment pf = (IPackageFragment) element; Module mod = asSourceModule(pf); if (mod!=null) { return new WithSourceFolder(mod, sourceFolder); } IPackageFragmentRoot pfr = (IPackageFragmentRoot) pf.getAncestor(PACKAGE_FRAGMENT_ROOT); if (pfr!=null && pfr.getPath().getFileExtension()!=null) { return new WithSourceFolder(pfr, sourceFolder); } //otherwise, it doesn't belong to a module if (level==LEVEL_MODULE) { return null; } return sourceFolder==null ? ArchiveMatches.INSTANCE : sourceFolder; } if (element instanceof IPackageFragmentRoot) { if (level==LEVEL_MODULE) { return null; } return sourceFolder==null ? ArchiveMatches.INSTANCE : sourceFolder; } if (element instanceof IFile) { if (level==LEVEL_FILE) { return null; } IFile file = (IFile) element; IContainer parent = file.getParent(); if (parent instanceof IFolder) { //let's see if the .java files it belongs to //a Ceylon package IFolder folder = (IFolder) parent; Package pack = getPackage(folder); if (pack!=null) { return new WithSourceFolder(pack, sourceFolder); } } return new WithSourceFolder( JavaCore.create(parent), sourceFolder); } return sourceFolder; } if (child instanceof IType || child instanceof IMethod || child instanceof IField || child instanceof IImportDeclaration) { IJavaElement javaElement = (IJavaElement) child; IJavaModelAware<IProject,ITypeRoot,IJavaElement> unit = CeylonBuilder.getUnit(javaElement); if (unit instanceof CeylonBinaryUnit || unit instanceof JavaClassFile) { IdeUnit ideUnit = (IdeUnit) unit; Module module = ideUnit.getPackage().getModule(); if (module instanceof BaseIdeModule) { BaseIdeModule jdtModule = (BaseIdeModule) module; if (jdtModule.getIsCeylonBinaryArchive()) { return new WithSourceFolder(unit, null); } } } IFile file = (IFile) javaElement.getResource(); //there is never a Unit for a .java file, since //I can't figure out any way to navigate to the //Java source file if (file == null) { if (level==LEVEL_FILE) { return null; } IPackageFragment pack = (IPackageFragment) javaElement.getAncestor( PACKAGE_FRAGMENT); return new WithSourceFolder(pack, null); } else { IPackageFragmentRoot sourceFolder = (IPackageFragmentRoot) javaElement.getAncestor( PACKAGE_FRAGMENT_ROOT); return new WithSourceFolder(file, sourceFolder); } } if (child instanceof CeylonElement) { CeylonElement ceylonElement = (CeylonElement) child; IFile file = ceylonElement.getFile(); if (file!=null) { //workspace .ceylon file IJavaElement javaElement = JavaCore.create(file.getParent()); IPackageFragmentRoot sourceFolder = (IPackageFragmentRoot) javaElement.getAncestor( PACKAGE_FRAGMENT_ROOT); IResourceAware<IProject,IFolder,IFile> unit = getUnit(file); if (unit instanceof Unit) { return new WithSourceFolder(unit, sourceFolder); } } else { //archive VirtualFile virtualFile = ceylonElement.getVirtualFile(); Unit unit = getUnit(virtualFile); if (unit!=null) { return new WithSourceFolder(unit, null); } } } return null; } @Override public void clear() { initialize(result); viewer.refresh(); } @Override public void setLevel(int grouping) { this.level = grouping; initialize(result); viewer.refresh(); } @Override public void setShowCategories(boolean showCategories) { this.showCategories = showCategories; initialize(result); viewer.refresh(); } }