/* * Copyright (c) 2014, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.typehierarchy; import com.google.common.base.Predicate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.dart.server.GetTypeHierarchyConsumer; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.ui.internal.text.functions.PositionElement; import org.apache.commons.lang3.ArrayUtils; import org.dartlang.analysis.server.protocol.Element; import org.dartlang.analysis.server.protocol.RequestError; import org.dartlang.analysis.server.protocol.TypeHierarchyItem; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public class TypeHierarchyContentProvider_NEW implements ITreeContentProvider { public class TypeItem { final TypeHierarchyItem type; final Element element; final int[] mixins; final int[] interfaces; public TypeItem(TypeHierarchyItem type) { this.type = type; this.element = type.getClassElement(); this.mixins = type.getMixins(); this.interfaces = type.getInterfaces(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof TypeItem) { return ((TypeItem) obj).element.equals(element); } return false; } public Element getElement() { return element; } /** * @return the {@link Element} for given selection. */ public Element getElementToOpen() { Element memberElement = type.getMemberElement(); if (memberElement != null) { return memberElement; } return type.getClassElement(); } @Override public int hashCode() { return element.hashCode(); } @Override public String toString() { return element.toString(); } public StyledString toStyledString() { StyledString styledString = new StyledString(type.getBestName()); if (mixins != null && mixins.length != 0) { styledString.append(" with ", StyledString.QUALIFIER_STYLER); appendItems(styledString, mixins); } if (interfaces != null && interfaces.length != 0) { styledString.append(" implements ", StyledString.QUALIFIER_STYLER); appendItems(styledString, interfaces); } return styledString; } private void appendItems(StyledString styledString, int[] ids) { for (int i = 0; i < ids.length; i++) { int id = ids[i]; TypeHierarchyItem item = items.get(id); if (i != 0) { styledString.append(", "); } styledString.append(item.getBestName()); } } } private List<TypeHierarchyItem> items = Lists.newArrayList(); private final Set<TypeHierarchyItem> seenItems = Sets.newHashSet(); private final List<TypeItem> superList = Lists.newArrayList(); private final Map<TypeItem, List<TypeItem>> superToSubsMap = Maps.newHashMap(); private final Map<TypeItem, TypeItem> subToSuperMap = Maps.newHashMap(); private boolean isMemberHierarchy; @Override public void dispose() { } @Override public Object[] getChildren(Object parentElement) { // super { int superIndex = superList.indexOf(parentElement); if (superIndex >= 0 && superIndex < superList.size() - 1) { return new Object[] {superList.get(superIndex + 1)}; } } // subs { List<TypeItem> subs = superToSubsMap.get(parentElement); if (subs != null) { return subs.toArray(new TypeItem[subs.size()]); } } // no children return ArrayUtils.EMPTY_OBJECT_ARRAY; } @Override public Object[] getElements(Object inputElement) { if (!superList.isEmpty()) { return new Object[] {superList.get(0)}; } return ArrayUtils.EMPTY_OBJECT_ARRAY; } @Override public Object getParent(Object element) { int superIndex = superList.indexOf(element); if (superIndex >= 0 && superIndex < superList.size()) { if (superIndex == 0) { return null; } return superList.get(superIndex - 1); } return subToSuperMap.get(element); } @Override public boolean hasChildren(Object element) { return getChildren(element).length != 0; } @Override public void inputChanged(final Viewer viewer, Object oldInput, Object newInput) { seenItems.clear(); superList.clear(); superToSubsMap.clear(); subToSuperMap.clear(); if (!(newInput instanceof PositionElement)) { return; } PositionElement element = (PositionElement) newInput; DartCore.getAnalysisServer().search_getTypeHierarchy( element.file, element.offset, false, new GetTypeHierarchyConsumer() { @Override public void computedHierarchy(List<TypeHierarchyItem> computedItems) { items = computedItems; inputHierarchyChanged(viewer); } @Override public void onError(RequestError requestError) { } }); } /** * @return the {@link Element} for given selection. */ Object convertSelectedElement(Object o) { return ((TypeItem) o).getElementToOpen(); } /** * @return the {@link Predicate} to check if given object is not interesting part of hierarchy, so * should be displayed using light color. */ Predicate<Object> getLightPredicate() { return new Predicate<Object>() { @Override public boolean apply(Object input) { if (!isMemberHierarchy) { return false; } TypeItem item = (TypeItem) input; return item.type.getMemberElement() == null; } }; } void inputHierarchyChanged(final Viewer viewer) { if (items.isEmpty()) { return; } TypeHierarchyItem target = items.get(0); isMemberHierarchy = target.getMemberElement() != null; final TypeItem targetItem = new TypeItem(target); // fill super hierarchy TypeHierarchyItem superType = target; while (superType != null) { if (!seenItems.add(superType)) { break; } TypeItem superItem = new TypeItem(superType); superList.add(0, superItem); if (superType == target) { superItem = targetItem; } // try "extends" { Integer superId = superItem.type.getSuperclass(); if (superId == null) { break; } superType = items.get(superId); } // try to use something better than "Object" if (superType.getClassElement().getName().equals("Object")) { if (superItem.mixins.length != 0) { int id = superItem.mixins[0]; superType = items.get(id); } else if (superItem.interfaces.length != 0) { int id = superItem.interfaces[0]; superType = items.get(id); } } } // prepare sub types fillSubTypes(target, targetItem); if (isMemberHierarchy) { keepBranchesWithMemberOverride(targetItem); } // refresh viewer Display.getDefault().asyncExec(new Runnable() { @Override public void run() { Control control = viewer.getControl(); if (control != null && !control.isDisposed()) { viewer.refresh(); if (viewer instanceof TreeViewer) { ((TreeViewer) viewer).expandAll(); viewer.setSelection(new StructuredSelection(targetItem), true); } } } }); } /** * Builds complete sub-types hierarchy in {@link #superToSubsMap}. */ private void fillSubTypes(TypeHierarchyItem type, TypeItem item) { int[] subIds = type.getSubclasses(); TypeHierarchyItem[] subTypes = new TypeHierarchyItem[subIds.length]; for (int i = 0; i < subIds.length; i++) { int id = subIds[i]; subTypes[i] = items.get(id); } Arrays.sort(subTypes, new Comparator<TypeHierarchyItem>() { @Override public int compare(TypeHierarchyItem o1, TypeHierarchyItem o2) { String name1 = o1.getClassElement().getName(); String name2 = o2.getClassElement().getName(); return name1.compareToIgnoreCase(name2); } }); List<TypeItem> subItems = Lists.newArrayList(); for (TypeHierarchyItem subType : subTypes) { if (seenItems.add(subType)) { TypeItem subItem = new TypeItem(subType); subToSuperMap.put(subItem, item); subItems.add(subItem); fillSubTypes(subType, subItem); } } superToSubsMap.put(item, subItems); } /** * @return <code>true</code> if branch of given {@link TypeItem} has override for member. */ private boolean keepBranchesWithMemberOverride(TypeItem typeItem) { List<TypeItem> subTypeItems = superToSubsMap.get(typeItem); if (subTypeItems != null) { for (Iterator<TypeItem> I = subTypeItems.iterator(); I.hasNext();) { TypeItem subTypeItem = I.next(); if (!keepBranchesWithMemberOverride(subTypeItem)) { I.remove(); superToSubsMap.remove(subTypeItem); } } if (!subTypeItems.isEmpty()) { return true; } } return typeItem.type.getMemberElement() != null; } }