/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.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/org/documents/epl-v10.php * * 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.android.ide.eclipse.adt.internal.lint; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutActionBar; import com.android.tools.lint.client.api.Configuration; import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.client.api.LintClient; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Severity; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.jface.viewers.TreeNodeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.TreeEvent; import org.eclipse.swt.events.TreeListener; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.IWorkbenchSiteProgressService; import org.eclipse.ui.progress.WorkbenchJob; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A tree-table widget which shows a list of lint warnings for an underlying * {@link IResource} such as a file, a project, or a list of projects. */ class LintList extends Composite implements IResourceChangeListener, ControlListener { private static final Object UPDATE_MARKERS_FAMILY = new Object(); // For persistence: private static final String KEY_WIDTHS = "lintColWidth"; //$NON-NLS-1$ private static final String KEY_VISIBLE = "lintColVisible"; //$NON-NLS-1$ // Mapping SWT TreeColumns to LintColumns private static final String KEY_COLUMN = "lintColumn"; //$NON-NLS-1$ private final IWorkbenchPartSite mSite; private final TreeViewer mTreeViewer; private final Tree mTree; private Set<String> mExpandedIds; private ContentProvider mContentProvider; private String mSelectedId; private List<? extends IResource> mResources; private Configuration mConfiguration; private final boolean mSingleFile; private int mErrorCount; private int mWarningCount; private final UpdateMarkersJob mUpdateMarkersJob = new UpdateMarkersJob(); private final IssueRegistry mRegistry; private final IMemento mMemento; private final LintColumn mMessageColumn = new LintColumn.MessageColumn(this); private final LintColumn mLineColumn = new LintColumn.LineColumn(this); private final LintColumn[] mColumns = new LintColumn[] { mMessageColumn, new LintColumn.PriorityColumn(this), new LintColumn.CategoryColumn(this), new LintColumn.LocationColumn(this), new LintColumn.FileColumn(this), new LintColumn.PathColumn(this), mLineColumn }; private LintColumn[] mVisibleColumns; LintList(IWorkbenchPartSite site, Composite parent, IMemento memento, boolean singleFile) { super(parent, SWT.NONE); mSingleFile = singleFile; mMemento = memento; mSite = site; mRegistry = EclipseLintClient.getRegistry(); GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginWidth = 0; gridLayout.marginHeight = 0; setLayout(gridLayout); mTreeViewer = new TreeViewer(this, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI); mTree = mTreeViewer.getTree(); mTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); createColumns(); mTreeViewer.setComparator(new TableComparator()); setSortIndicators(); mContentProvider = new ContentProvider(); mTreeViewer.setContentProvider(mContentProvider); mTree.setLinesVisible(true); mTree.setHeaderVisible(true); mTree.addControlListener(this); ResourcesPlugin.getWorkspace().addResourceChangeListener( this, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_BUILD | IResourceChangeEvent.POST_BUILD); // Workaround for https://bugs.eclipse.org/341865 mTree.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { mTreePainted = true; mTreeViewer.getTree().removePaintListener(this); } }); // Remember the most recently selected id category such that we can // attempt to reselect it after a refresh mTree.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { List<IMarker> markers = getSelectedMarkers(); if (markers.size() > 0) { mSelectedId = EclipseLintClient.getId(markers.get(0)); } } }); mTree.addTreeListener(new TreeListener() { @Override public void treeExpanded(TreeEvent e) { Object data = e.item.getData(); if (data instanceof IMarker) { String id = EclipseLintClient.getId((IMarker) data); if (id != null) { if (mExpandedIds == null) { mExpandedIds = new HashSet<String>(); } mExpandedIds.add(id); } } } @Override public void treeCollapsed(TreeEvent e) { if (mExpandedIds != null) { Object data = e.item.getData(); if (data instanceof IMarker) { String id = EclipseLintClient.getId((IMarker) data); if (id != null) { mExpandedIds.remove(id); } } } } }); } private boolean mTreePainted; private void updateColumnWidths() { Rectangle r = mTree.getClientArea(); int availableWidth = r.width; // Add all available size to the first column for (int i = 1; i < mTree.getColumnCount(); i++) { TreeColumn column = mTree.getColumn(i); availableWidth -= column.getWidth(); } if (availableWidth > 100) { mTree.getColumn(0).setWidth(availableWidth); } } public void setResources(List<? extends IResource> resources) { mResources = resources; mConfiguration = null; for (IResource resource : mResources) { IProject project = resource.getProject(); if (project != null) { // For logging only LintClient client = new EclipseLintClient(null, null, null, false); mConfiguration = ProjectLintConfiguration.get(client, project, false); break; } } if (mConfiguration == null) { mConfiguration = GlobalLintConfiguration.get(); } List<IMarker> markerList = getMarkers(); mTreeViewer.setInput(markerList); if (mSingleFile) { expandAll(); } // Selecting the first item isn't a good idea since it may not be the first // item shown in the table (since it does its own sorting), and furthermore we // may not have all the data yet; this is called when scanning begins, not when // it's done: //if (mTree.getItemCount() > 0) { // mTree.select(mTree.getItem(0)); //} updateColumnWidths(); // in case mSingleFile changed } /** Select the first item */ public void selectFirst() { if (mTree.getItemCount() > 0) { mTree.select(mTree.getItem(0)); } } private List<IMarker> getMarkers() { mErrorCount = mWarningCount = 0; List<IMarker> markerList = new ArrayList<IMarker>(); if (mResources != null) { for (IResource resource : mResources) { IMarker[] markers = EclipseLintClient.getMarkers(resource); for (IMarker marker : markers) { markerList.add(marker); int severity = marker.getAttribute(IMarker.SEVERITY, 0); if (severity == IMarker.SEVERITY_ERROR) { mErrorCount++; } else if (severity == IMarker.SEVERITY_WARNING) { mWarningCount++; } } } // No need to sort the marker list here; it will be sorted by the tree table model } return markerList; } public int getErrorCount() { return mErrorCount; } public int getWarningCount() { return mWarningCount; } @Override protected void checkSubclass() { // Disable the check that prevents subclassing of SWT components } public void addSelectionListener(SelectionListener listener) { mTree.addSelectionListener(listener); } public void refresh() { mTreeViewer.refresh(); } public List<IMarker> getSelectedMarkers() { TreeItem[] selection = mTree.getSelection(); List<IMarker> markers = new ArrayList<IMarker>(selection.length); for (TreeItem item : selection) { Object data = item.getData(); if (data instanceof IMarker) { markers.add((IMarker) data); } } return markers; } @Override public void dispose() { cancelJobs(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); super.dispose(); } private class ContentProvider extends TreeNodeContentProvider { private Map<Object, Object[]> mChildren; private Map<IMarker, Integer> mTypeCount; private IMarker[] mTopLevels; @Override public Object[] getElements(Object inputElement) { if (inputElement == null) { mTypeCount = null; return new IMarker[0]; } @SuppressWarnings("unchecked") List<IMarker> list = (List<IMarker>) inputElement; // Partition the children such that at the top level we have one // marker of each type, and below we have all the duplicates of // each one of those errors. And for errors with multiple locations, // there is a third level. Multimap<String, IMarker> types = ArrayListMultimap.<String, IMarker>create(100, 20); for (IMarker marker : list) { String id = EclipseLintClient.getId(marker); types.put(id, marker); } Set<String> ids = types.keySet(); mChildren = new HashMap<Object, Object[]>(ids.size()); mTypeCount = new HashMap<IMarker, Integer>(ids.size()); List<IMarker> topLevel = new ArrayList<IMarker>(ids.size()); for (String id : ids) { Collection<IMarker> markers = types.get(id); int childCount = markers.size(); // Must sort the list items in order to have a stable first item // (otherwise preserving expanded paths etc won't work) TableComparator sorter = getTableSorter(); IMarker[] array = markers.toArray(new IMarker[markers.size()]); sorter.sort(mTreeViewer, array); IMarker topMarker = array[0]; mTypeCount.put(topMarker, childCount); topLevel.add(topMarker); IMarker[] children = Arrays.copyOfRange(array, 1, array.length); mChildren.put(topMarker, children); } mTopLevels = topLevel.toArray(new IMarker[topLevel.size()]); return mTopLevels; } @Override public boolean hasChildren(Object element) { Object[] children = mChildren != null ? mChildren.get(element) : null; return children != null && children.length > 0; } @Override public Object[] getChildren(Object parentElement) { Object[] children = mChildren.get(parentElement); if (children != null) { return children; } return new Object[0]; } @Override public Object getParent(Object element) { return null; } public int getCount(IMarker marker) { if (mTypeCount != null) { Integer count = mTypeCount.get(marker); if (count != null) { return count.intValue(); } } return -1; } IMarker[] getTopMarkers() { return mTopLevels; } } private class LintColumnLabelProvider extends StyledCellLabelProvider { private LintColumn mColumn; LintColumnLabelProvider(LintColumn column) { mColumn = column; } @Override public void update(ViewerCell cell) { Object element = cell.getElement(); cell.setImage(mColumn.getImage((IMarker) element)); StyledString styledString = mColumn.getStyledValue((IMarker) element); if (styledString == null) { cell.setText(mColumn.getValue((IMarker) element)); cell.setStyleRanges(null); } else { cell.setText(styledString.toString()); cell.setStyleRanges(styledString.getStyleRanges()); } super.update(cell); } } TreeViewer getTreeViewer() { return mTreeViewer; } Tree getTree() { return mTree; } // ---- Implements IResourceChangeListener ---- @Override public void resourceChanged(IResourceChangeEvent event) { if (mResources == null) { return; } IMarkerDelta[] deltas = event.findMarkerDeltas(AdtConstants.MARKER_LINT, true); if (deltas.length > 0) { // Update immediately for POST_BUILD events, otherwise do an unconditional // update after 30 seconds. This matches the logic in Eclipse's ProblemView // (see the MarkerView class). if (event.getType() == IResourceChangeEvent.POST_BUILD) { cancelJobs(); getProgressService().schedule(mUpdateMarkersJob, 100); } else { IWorkbenchSiteProgressService progressService = getProgressService(); if (progressService == null) { mUpdateMarkersJob.schedule(30000); } else { getProgressService().schedule(mUpdateMarkersJob, 30000); } } } } // ---- Implements ControlListener ---- @Override public void controlMoved(ControlEvent e) { } @Override public void controlResized(ControlEvent e) { updateColumnWidths(); } // ---- Updating Markers ---- private void cancelJobs() { mUpdateMarkersJob.cancel(); } protected IWorkbenchSiteProgressService getProgressService() { if (mSite != null) { Object siteService = mSite.getAdapter(IWorkbenchSiteProgressService.class); if (siteService != null) { return (IWorkbenchSiteProgressService) siteService; } } return null; } private class UpdateMarkersJob extends WorkbenchJob { UpdateMarkersJob() { super("Updating Lint Markers"); setSystem(true); } @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (mTree.isDisposed()) { return Status.CANCEL_STATUS; } mTreeViewer.setInput(null); List<IMarker> markerList = getMarkers(); if (markerList.size() == 0) { LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(AdtUtils.getActiveEditor()); if (delegate != null) { GraphicalEditorPart g = delegate.getGraphicalEditor(); assert g != null; LayoutActionBar bar = g == null ? null : g.getLayoutActionBar(); assert bar != null; if (bar != null) { bar.updateErrorIndicator(); } } } // Trigger selection update Event updateEvent = new Event(); updateEvent.widget = mTree; mTree.notifyListeners(SWT.Selection, updateEvent); mTreeViewer.setInput(markerList); mTreeViewer.refresh(); if (mExpandedIds != null) { List<IMarker> expanded = new ArrayList<IMarker>(mExpandedIds.size()); IMarker[] topMarkers = mContentProvider.getTopMarkers(); if (topMarkers != null) { for (IMarker marker : topMarkers) { String id = EclipseLintClient.getId(marker); if (id != null && mExpandedIds.contains(id)) { expanded.add(marker); } } } if (!expanded.isEmpty()) { mTreeViewer.setExpandedElements(expanded.toArray()); } } if (mSelectedId != null) { IMarker[] topMarkers = mContentProvider.getTopMarkers(); for (IMarker marker : topMarkers) { if (mSelectedId.equals(EclipseLintClient.getId(marker))) { mTreeViewer.setSelection(new StructuredSelection(marker), true /*reveal*/); break; } } } return Status.OK_STATUS; } @Override public boolean shouldRun() { // Do not run if the change came in before there is a viewer return PlatformUI.isWorkbenchRunning(); } @Override public boolean belongsTo(Object family) { return UPDATE_MARKERS_FAMILY == family; } } /** * Returns the list of resources being shown in the list * * @return the list of resources being shown in this composite */ public List<? extends IResource> getResources() { return mResources; } /** Expands all nodes */ public void expandAll() { mTreeViewer.expandAll(); if (mExpandedIds == null) { mExpandedIds = new HashSet<String>(); } IMarker[] topMarkers = mContentProvider.getTopMarkers(); if (topMarkers != null) { for (IMarker marker : topMarkers) { String id = EclipseLintClient.getId(marker); if (id != null) { mExpandedIds.add(id); } } } } /** Collapses all nodes */ public void collapseAll() { mTreeViewer.collapseAll(); mExpandedIds = null; } // ---- Column Persistence ---- public void saveState(IMemento memento) { if (mSingleFile) { // Don't use persistence for single-file lists: this is a special mode of the // window where we show a hardcoded set of columns for a single file, deliberately // omitting the location column etc return; } IMemento columnEntry = memento.createChild(KEY_WIDTHS); LintColumn[] columns = new LintColumn[mTree.getColumnCount()]; int[] positions = mTree.getColumnOrder(); for (int i = 0; i < columns.length; i++) { TreeColumn treeColumn = mTree.getColumn(i); LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN); // Workaround for TeeColumn.getWidth() returning 0 in some cases, // see https://bugs.eclipse.org/341865 for details. int width = getColumnWidth(column, mTreePainted); columnEntry.putInteger(getKey(treeColumn), width); columns[positions[i]] = column; } if (getVisibleColumns() != null) { IMemento visibleEntry = memento.createChild(KEY_VISIBLE); for (LintColumn column : getVisibleColumns()) { visibleEntry.putBoolean(getKey(column), true); } } } private void createColumns() { LintColumn[] columns = getVisibleColumns(); TableLayout layout = new TableLayout(); for (int i = 0; i < columns.length; i++) { LintColumn column = columns[i]; TreeViewerColumn viewerColumn = null; TreeColumn treeColumn; viewerColumn = new TreeViewerColumn(mTreeViewer, SWT.NONE); treeColumn = viewerColumn.getColumn(); treeColumn.setData(KEY_COLUMN, column); treeColumn.setResizable(true); treeColumn.addSelectionListener(getHeaderListener()); if (!column.isLeftAligned()) { treeColumn.setAlignment(SWT.RIGHT); } viewerColumn.setLabelProvider(new LintColumnLabelProvider(column)); treeColumn.setText(column.getColumnHeaderText()); treeColumn.setImage(column.getColumnHeaderImage()); IMemento columnWidths = null; if (mMemento != null && !mSingleFile) { columnWidths = mMemento.getChild(KEY_WIDTHS); } int columnWidth = getColumnWidth(column, false); if (columnWidths != null) { columnWidths.putInteger(getKey(column), columnWidth); } if (i == 0) { // The first column should use layout -weights- to get all the // remaining room layout.addColumnData(new ColumnWeightData(1, true)); } else if (columnWidth < 0) { int defaultColumnWidth = column.getPreferredWidth(); layout.addColumnData(new ColumnPixelData(defaultColumnWidth, true, true)); } else { layout.addColumnData(new ColumnPixelData(columnWidth, true)); } } mTreeViewer.getTree().setLayout(layout); mTree.layout(true); } private int getColumnWidth(LintColumn column, boolean getFromUi) { Tree tree = mTreeViewer.getTree(); if (getFromUi) { TreeColumn[] columns = tree.getColumns(); for (int i = 0; i < columns.length; i++) { if (column.equals(columns[i].getData(KEY_COLUMN))) { return columns[i].getWidth(); } } } int preferredWidth = -1; if (mMemento != null && !mSingleFile) { IMemento columnWidths = mMemento.getChild(KEY_WIDTHS); if (columnWidths != null) { Integer value = columnWidths.getInteger(getKey(column)); // Make sure we get a useful value if (value != null && value.intValue() >= 0) preferredWidth = value.intValue(); } } if (preferredWidth <= 0) { preferredWidth = Math.max(column.getPreferredWidth(), 30); } return preferredWidth; } private static String getKey(TreeColumn treeColumn) { return getKey((LintColumn) treeColumn.getData(KEY_COLUMN)); } private static String getKey(LintColumn column) { return column.getClass().getSimpleName(); } private LintColumn[] getVisibleColumns() { if (mVisibleColumns == null) { if (mSingleFile) { // Special mode where we show just lint warnings for a single file: // use a hardcoded list of columns, not including path/location etc but // including line numbers (which are normally not shown by default). mVisibleColumns = new LintColumn[] { mMessageColumn, mLineColumn }; } else { // Generate visible columns based on (a) previously saved window state, // and (b) default window visible states provided by the columns themselves List<LintColumn> list = new ArrayList<LintColumn>(); IMemento visibleColumns = null; if (mMemento != null) { visibleColumns = mMemento.getChild(KEY_VISIBLE); } for (LintColumn column : mColumns) { if (visibleColumns != null) { Boolean b = visibleColumns.getBoolean(getKey(column)); if (b != null && b.booleanValue()) { list.add(column); } } else if (column.visibleByDefault()) { list.add(column); } } if (!list.contains(mMessageColumn)) { list.add(0, mMessageColumn); } mVisibleColumns = list.toArray(new LintColumn[list.size()]); } } return mVisibleColumns; } int getCount(IMarker marker) { return mContentProvider.getCount(marker); } Issue getIssue(String id) { return mRegistry.getIssue(id); } Issue getIssue(IMarker marker) { String id = EclipseLintClient.getId(marker); return mRegistry.getIssue(id); } Severity getSeverity(Issue issue) { return mConfiguration.getSeverity(issue); } // ---- Choosing visible columns ---- public void configureColumns() { ColumnDialog dialog = new ColumnDialog(getShell(), mColumns, getVisibleColumns()); if (dialog.open() == Window.OK) { mVisibleColumns = dialog.getSelectedColumns(); // Clear out columns: Must recreate to set the right label provider etc for (TreeColumn column : mTree.getColumns()) { column.dispose(); } createColumns(); mTreeViewer.setComparator(new TableComparator()); setSortIndicators(); mTreeViewer.refresh(); } } // ---- Table Sorting ---- private SelectionListener getHeaderListener() { return new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final TreeColumn treeColumn = (TreeColumn) e.widget; final LintColumn column = (LintColumn) treeColumn.getData(KEY_COLUMN); try { IWorkbenchSiteProgressService progressService = getProgressService(); if (progressService == null) { BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() { @Override public void run() { resortTable(treeColumn, column, new NullProgressMonitor()); } }); } else { getProgressService().busyCursorWhile(new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) { resortTable(treeColumn, column, monitor); } }); } } catch (InvocationTargetException e1) { AdtPlugin.log(e1, null); } catch (InterruptedException e1) { return; } } private void resortTable(final TreeColumn treeColumn, LintColumn column, IProgressMonitor monitor) { TableComparator sorter = getTableSorter(); monitor.beginTask("Sorting", 100); monitor.worked(10); if (column.equals(sorter.getTopColumn())) { sorter.reverseTopPriority(); } else { sorter.setTopPriority(column); } monitor.worked(15); PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { mTreeViewer.refresh(); updateDirectionIndicator(treeColumn); } }); monitor.done(); } }; } private void setSortIndicators() { LintColumn top = getTableSorter().getTopColumn(); TreeColumn[] columns = mTreeViewer.getTree().getColumns(); for (int i = 0; i < columns.length; i++) { TreeColumn column = columns[i]; if (column.getData(KEY_COLUMN).equals(top)) { updateDirectionIndicator(column); return; } } } private void updateDirectionIndicator(TreeColumn column) { Tree tree = mTreeViewer.getTree(); tree.setSortColumn(column); if (getTableSorter().isAscending()) { tree.setSortDirection(SWT.UP); } else { tree.setSortDirection(SWT.DOWN); } } private TableComparator getTableSorter() { return (TableComparator) mTreeViewer.getComparator(); } /** Comparator used to sort the {@link LintList} tree. * <p> * This code is simplified from similar code in * org.eclipse.ui.views.markers.internal.TableComparator */ private class TableComparator extends ViewerComparator { private int[] mPriorities; private boolean[] mDirections; private int[] mDefaultPriorities; private boolean[] mDefaultDirections; private TableComparator() { int[] defaultPriorities = new int[mColumns.length]; for (int i = 0; i < defaultPriorities.length; i++) { defaultPriorities[i] = i; } mPriorities = defaultPriorities; boolean[] directions = new boolean[mColumns.length]; for (int i = 0; i < directions.length; i++) { directions[i] = mColumns[i].isAscending(); } mDirections = directions; mDefaultPriorities = new int[defaultPriorities.length]; System.arraycopy(defaultPriorities, 0, this.mDefaultPriorities, 0, defaultPriorities.length); mDefaultDirections = new boolean[directions.length]; System.arraycopy(directions, 0, this.mDefaultDirections, 0, directions.length); } private void resetState() { System.arraycopy(mDefaultPriorities, 0, mPriorities, 0, mPriorities.length); System.arraycopy(mDefaultDirections, 0, mDirections, 0, mDirections.length); } private void reverseTopPriority() { mDirections[mPriorities[0]] = !mDirections[mPriorities[0]]; } private void setTopPriority(LintColumn property) { for (int i = 0; i < mColumns.length; i++) { if (mColumns[i].equals(property)) { setTopPriority(i); return; } } } private void setTopPriority(int priority) { if (priority < 0 || priority >= mPriorities.length) { return; } int index = -1; for (int i = 0; i < mPriorities.length; i++) { if (mPriorities[i] == priority) { index = i; } } if (index == -1) { resetState(); return; } // shift the array for (int i = index; i > 0; i--) { mPriorities[i] = mPriorities[i - 1]; } mPriorities[0] = priority; mDirections[priority] = mDefaultDirections[priority]; } private boolean isAscending() { return mDirections[mPriorities[0]]; } private int getTopPriority() { return mPriorities[0]; } private LintColumn getTopColumn() { return mColumns[getTopPriority()]; } @Override public int compare(Viewer viewer, Object e1, Object e2) { return compare((IMarker) e1, (IMarker) e2, 0, true); } private int compare(IMarker marker1, IMarker marker2, int depth, boolean continueSearching) { if (depth >= mPriorities.length) { return 0; } int column = mPriorities[depth]; LintColumn property = mColumns[column]; int result = property.compare(marker1, marker2); if (result == 0 && continueSearching) { return compare(marker1, marker2, depth + 1, continueSearching); } return result * (mDirections[column] ? 1 : -1); } } }