JavaHelperView.java

/*******************************************************************************
 * Copyright (c) 2020 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.ui.helper.views;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICodeAssist;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.SourceType;
//import org.eclipse.jdt.jeview.views.JEResource;
//import org.eclipse.jdt.jeview.views.JERoot;
//import org.eclipse.jdt.jeview.views.JavaElement;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ContributionItemFactory;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.sandbox.jdt.ui.helper.views.colum.AbstractColumn;
import org.sandbox.jdt.ui.helper.views.colum.DeclaringMethodColumn;
import org.sandbox.jdt.ui.helper.views.colum.DeprecatedColumn;
import org.sandbox.jdt.ui.helper.views.colum.NameColumn;
import org.sandbox.jdt.ui.helper.views.colum.PackageColumn;
import org.sandbox.jdt.ui.helper.views.colum.QualifiednameColumn;

public class JavaHelperView extends ViewPart implements IShowInSource, IShowInTarget {

	Logger logger= PlatformUI.getWorkbench().getService(Logger.class);
	TableViewer tableViewer;
	private Table table;
	//	private JERoot fInput;
	private Action fRefreshAction;
	private Action fResetAction;
	private Action fElementAtAction;
	private Action fPropertiesAction;
	//	private Action fFocusAction;

	private Action fCodeSelectAction;

	private IPartListener2 partListener;

	@Override
	public void createPartControl(Composite parent) {
		parent.setLayout(new GridLayout(1, false));

		tableViewer= new TableViewer(parent, SWT.BORDER | SWT.FULL_SELECTION);
		tableViewer.setColumnProperties(new String[] {});
		tableViewer.setUseHashlookup(true);
		table= tableViewer.getTable();
		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
		table.setHeaderVisible(true);
		table.setHeaderBackground(parent.getDisplay().getSystemColor(SWT.COLOR_TITLE_BACKGROUND));
		table.setLinesVisible(true);
		tableViewer.setContentProvider(new JHViewContentProvider());
		// This will create the columns for the table
		AbstractColumn.addColumn(tableViewer, new NameColumn());
		AbstractColumn.addColumn(tableViewer, new QualifiednameColumn());
		AbstractColumn.addColumn(tableViewer, new PackageColumn());
		AbstractColumn.addColumn(tableViewer, new DeprecatedColumn());
		AbstractColumn.addColumn(tableViewer, new DeclaringMethodColumn());

		tableViewer.setComparator(AbstractColumn.getComparator());
		reset();
		makeActions();
		hookContextMenu();
		getSite().setSelectionProvider(new JHViewSelectionProvider(tableViewer));
		contributeToActionBars();
		// tableViewer.addSelectionChangedListener(event -> fCopyAction.setEnabled(!
		// event.getSelection().isEmpty()));
		// tableViewer.addSelectionChangedListener(event -> fCopyAction.setEnabled(!
		// event.getSelection().isEmpty()));
		
		// Add part listener to track editor changes
		addPartListener();
	}

	private void contributeToActionBars() {
		IActionBars bars= getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
		bars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), fRefreshAction);
		// bars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction);
		bars.setGlobalActionHandler(ActionFactory.PROPERTIES.getId(), fPropertiesAction);
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(fCodeSelectAction);
		manager.add(fElementAtAction);
		manager.add(fResetAction);
		manager.add(fRefreshAction);
		manager.add(new Separator());
		// fDrillDownAdapter.addNavigationActions(manager);
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.add(fCodeSelectAction);
		manager.add(fElementAtAction);
		// manager.add(fCreateFromHandleAction);
		manager.add(fResetAction);
		// manager.add(fLogDeltasAction);
		manager.add(new Separator());
		manager.add(fRefreshAction);
	}

	private void makeActions() {
		fCodeSelectAction= new Action("Set Input from Editor (&codeSelect)", JHPluginImages.IMG_SET_FOCUS_CODE_SELECT) { //$NON-NLS-1$
			@Override
			public void run() {
				IEditorPart editor= getSite().getPage().getActiveEditor();
				if (editor == null) {
					setEmptyInput();
					return;
				}
				IEditorInput input= editor.getEditorInput();
				ISelectionProvider selectionProvider= editor.getSite().getSelectionProvider();
				if (input == null || selectionProvider == null) {
					setEmptyInput();
					return;
				}
				ISelection selection= selectionProvider.getSelection();
				if (!(selection instanceof ITextSelection)) {
					setEmptyInput();
					return;
				}
				IJavaElement javaElement= input.getAdapter(IJavaElement.class);
				if (javaElement == null) {
					setEmptyInput();
					return;
				}

				IJavaElement[] resolved;
				try {
					resolved= codeResolve(javaElement, (ITextSelection) selection);
				} catch (JavaModelException e) {
					setEmptyInput();
					return;
				}
				if (resolved.length == 0) {
					setEmptyInput();
					return;
				}

				List<IJavaElement> asList= Arrays.asList(resolved);

				setInput(asList);
			}
		};
		fCodeSelectAction.setToolTipText("Set input from current editor's selection (codeSelect)"); //$NON-NLS-1$
		//		fFocusAction = new Action() {
		//			@Override
		//			public void run() {
		//				Object selected = ((IStructuredSelection) tableViewer.getSelection()).getFirstElement();
		//				if (selected instanceof JavaElement) {
		//					setSingleInput((IJavaModel) ((JavaElement) selected).getJavaElement());
		//				} else if (selected instanceof JEResource) {
		//					setSingleInput((IJavaModel) ((JEResource) selected).getResource());
		//				}
		//			}
		//		};
		//		fFocusAction.setToolTipText("Focus on Selection");
		fPropertiesAction= new Action("&Properties", JHPluginImages.IMG_PROPERTIES) { //$NON-NLS-1$
			@Override
			public void run() {
				String viewId= IPageLayout.ID_PROP_SHEET;
				IWorkbenchPage page= getViewSite().getPage();
				IViewPart view;
				try {
					view= page.showView(viewId);
					page.activate(JavaHelperView.this);
					page.bringToTop(view);
				} catch (PartInitException e) {
					logger.error(e, "could not find Properties view"); //$NON-NLS-1$
				}
			}
		};
		fPropertiesAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_PROPERTIES);
		fResetAction= new Action("&Reset View", getJavaModelImageDescriptor()) { //$NON-NLS-1$
			@Override
			public void run() {
				reset();
			}
		};
		fResetAction.setToolTipText("Reset View to JavaModel"); //$NON-NLS-1$
		fRefreshAction= new Action("Re&fresh", JHPluginImages.IMG_REFRESH) { //$NON-NLS-1$
			@Override
			public void run() {
				BusyIndicator.showWhile(getSite().getShell().getDisplay(), () -> tableViewer.refresh());
			}
		};
		fRefreshAction.setToolTipText("Refresh"); //$NON-NLS-1$
		fRefreshAction.setActionDefinitionId("org.eclipse.ui.file.refresh"); //$NON-NLS-1$
		fElementAtAction= new Action("Set Input from Editor location (&getElementAt)", JHPluginImages.IMG_SET_FOCUS) { //$NON-NLS-1$
			@Override
			public void run() {
				IEditorPart editor= getSite().getPage().getActiveEditor();
				if (editor == null) {
					setEmptyInput();
					return;
				}
				IEditorInput input= editor.getEditorInput();
				ISelectionProvider selectionProvider= editor.getSite().getSelectionProvider();
				if (input == null || selectionProvider == null) {
					setEmptyInput();
					return;
				}
				ISelection selection= selectionProvider.getSelection();
				if (!(selection instanceof ITextSelection)) {
					setEmptyInput();
					return;
				}
				IJavaElement javaElement= input.getAdapter(IJavaElement.class);
				if (javaElement == null) {
					setEmptyInput();
					return;
				}

				IJavaElement resolved;
				try {
					resolved= getElementAtOffset(javaElement, (ITextSelection) selection);
				} catch (JavaModelException e) {
					setEmptyInput();
					return;
				}
				if (resolved == null) {
					setEmptyInput();
					return;
				}

				IResource correspondingResource= resolved.getResource();
				setSingleInput(correspondingResource);

			}

		};
		fElementAtAction.setToolTipText("Set input from current editor's selection location (getElementAt)"); //$NON-NLS-1$
	}

	void setEmptyInput() {
		setInput(Collections.<IJavaModel>emptyList());
	}

	static IJavaElement[] codeResolve(IJavaElement input, ITextSelection selection) throws JavaModelException {
		if (input instanceof ICodeAssist) {
			if (input instanceof ICompilationUnit) {
				reconcile((ICompilationUnit) input);
			}
			IJavaElement[] elements= ((ICodeAssist) input).codeSelect(selection.getOffset(), selection.getLength());
			if (elements != null && elements.length > 0) {
				return elements;
			}
		}
		return new IJavaElement[0];
	}

	static IJavaElement getElementAtOffset(IJavaElement input, ITextSelection selection) throws JavaModelException {
		if (input instanceof ICompilationUnit cunit) {
			reconcile(cunit);
			IJavaElement ref= cunit.getElementAt(selection.getOffset());
			if (ref == null) {
				return input;
			}
			return ref;
		}
		if (input instanceof IClassFile) {
			IJavaElement ref= ((IClassFile) input).getElementAt(selection.getOffset());
			if (ref != null) {
				return ref;
			}
		}
		return input;
	}

	//	private void addFocusActionOrNot(IMenuManager manager) {
	//		if (tableViewer.getSelection() instanceof IStructuredSelection) {
	//			IStructuredSelection structuredSelection = (IStructuredSelection) tableViewer.getSelection();
	//			if (structuredSelection.size() == 1) {
	//				Object first = structuredSelection.getFirstElement();
	//				if (first instanceof JavaElement) {
	//					IJavaElement javaElement = ((JavaElement) first).getJavaElement();
	//					if (javaElement != null) {
	//						String name = javaElement.getElementName();
	//						fFocusAction.setText("Fo&cus On '" + name + '\'');
	//						manager.add(fFocusAction);
	//					}
	//				} else if (first instanceof JEResource) {
	//					IResource resource = ((JEResource) first).getResource();
	//					if (resource != null) {
	//						String name = resource.getName();
	//						fFocusAction.setText("Fo&cus On '" + name + '\'');
	//						manager.add(fFocusAction);
	//					}
	//				}
	//			}
	//		}
	//	}

	/* see JavaModelUtil.reconcile((ICompilationUnit) input) */
	static void reconcile(ICompilationUnit unit) throws JavaModelException {
		synchronized (unit) {
			unit.reconcile(ICompilationUnit.NO_AST, false /* don't force problem detection */,
					null /* use primary owner */, null /* no progress monitor */);
		}
	}

	private ImageDescriptor getJavaModelImageDescriptor() {
		JavaElementLabelProvider lp= new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_SMALL_ICONS);
		Image modelImage= lp.getImage(getJavaModel());
		ImageDescriptor modelImageDescriptor= ImageDescriptor.createFromImage(modelImage);
		lp.dispose();
		return modelImageDescriptor;
	}

	void reset() {
		setSingleInput(getJavaModel());
	}

	private IResource getJavaModel() {
		//		return JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
		return ResourcesPlugin.getWorkspace().getRoot();
		//		return null;
	}

	private void hookContextMenu() {
		MenuManager menuMgr= new MenuManager("#PopupMenu"); //$NON-NLS-1$
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(this::fillContextMenu);
		Menu menu= menuMgr.createContextMenu(tableViewer.getControl());
		tableViewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, tableViewer);
	}

	void fillContextMenu(IMenuManager manager) {
		//		addFocusActionOrNot(manager);
		manager.add(fResetAction);
		manager.add(fRefreshAction);
		manager.add(new Separator());

		if (!getSite().getSelectionProvider().getSelection().isEmpty()) {
			MenuManager showInSubMenu= new MenuManager(getShowInMenuLabel());
			IWorkbenchWindow workbenchWindow= getSite().getWorkbenchWindow();
			showInSubMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(workbenchWindow));
			manager.add(showInSubMenu);
		}
		// addElementActionsOrNot(manager);
		manager.add(new Separator());

		// manager.add(fCopyAction);
		manager.add(new Separator());

		// fDrillDownAdapter.addNavigationActions(manager);
		manager.add(new Separator());
		// Other plug-ins can contribute there actions here
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		manager.add(new Separator());
		// addCompareActionOrNot(manager);
		manager.add(fPropertiesAction);
	}

	private String getShowInMenuLabel() {
		String keyBinding= null;

		IBindingService bindingService= PlatformUI.getWorkbench().getAdapter(IBindingService.class);
		if (bindingService != null) {
			keyBinding= bindingService
					.getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.NAVIGATE_SHOW_IN_QUICK_MENU);
		}

		if (keyBinding == null) {
			keyBinding= ""; //$NON-NLS-1$
		}

		return "Sho&w In" + '\t' + keyBinding; //$NON-NLS-1$
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		tableViewer.getControl().setFocus();
	}

	@Override
	public boolean show(ShowInContext context) {
		ISelection selection= context.getSelection();
		if (selection instanceof IStructuredSelection structuredSelection) {
			if (structuredSelection.size() >= 1) {
				List<Object> input= new ArrayList<>();
				for (Object item : structuredSelection) {
					if (item instanceof IJavaElement || item instanceof IResource
							|| item instanceof IJarEntryResource) {
						input.add(item);
					}
				}
				if (!input.isEmpty()) {
					setInput(input);
					return true;
				}
			}
		}

		Object input= context.getInput();
		if (input instanceof IEditorInput) {
			SourceType elementOfInput= (SourceType) getElementOfInput((IEditorInput) context.getInput());
			if (elementOfInput != null) {
				//				setSingleInput(elementOfInput);
				return true;
			}
		}

		return false;
	}

	void setSingleInput(IResource iResource) {
		setInput(Collections.singletonList(iResource));
	}

	Object getElementOfInput(IEditorInput input) {
		Object adapted= input.getAdapter(IClassFile.class);
		if (adapted != null) {
			return adapted;
		}

		if (input instanceof IFileEditorInput) {
			IFile file= ((IFileEditorInput) input).getFile();
			IJavaElement javaElement= JavaCore.create(file);
			if (javaElement != null) {
				return javaElement;
			}
			return file;
		}
		if (input instanceof IStorageEditorInput) {
			try {
				return ((IStorageEditorInput) input).getStorage();
			} catch (CoreException e) {
			}
		}
		return null;
	}

	@Override
	public ShowInContext getShowInContext() {
		IWorkbenchPartSite site= getSite();
		if (site == null) {
			return null;
		}
		ISelectionProvider selectionProvider= site.getSelectionProvider();
		if (selectionProvider == null) {
			return null;
		}
		return new ShowInContext(null, selectionProvider.getSelection());
	}

	void setInput(List<?> javaElementsOrResources) {
		//		fInput = new JERoot(javaElementsOrResources);
		//		tableViewer.setInput(fInput);
		tableViewer.setInput(javaElementsOrResources);
		JHViewContentProvider tcp= (JHViewContentProvider) tableViewer.getContentProvider();
		Object[] elements= tcp.getElements(javaElementsOrResources);
		if (elements.length > 0) {
			tableViewer.setSelection(new StructuredSelection(elements[0]));
			// if (elements.length == 1) {
			// tableViewer.setExpandedState(elements[0], true);
			// }
		}
		// fDrillDownAdapter.reset();
	}

	@Override
	public <T> T getAdapter(Class<T> adapter) {
		// if (adapter == IPropertySheetPage.class) {
		// return (T) getPropertySheetPage();
		// }
		return super.getAdapter(adapter);
	}

	/**
	 * Adds a part listener to track when editors are activated and automatically
	 * refresh the view with the active editor's content.
	 */
	private void addPartListener() {
		// Check if listener is already registered to prevent duplicates
		if (partListener != null) {
			return;
		}
		
		partListener = new IPartListener2() {
			@Override
			public void partActivated(IWorkbenchPartReference partRef) {
				if (partRef.getPart(false) instanceof IEditorPart) {
					// Update the view when an editor is activated
					// Ensure UI updates happen on the UI thread
					getSite().getShell().getDisplay().asyncExec(() -> updateViewFromActiveEditor());
				}
			}

			@Override
			public void partBroughtToTop(IWorkbenchPartReference partRef) {
				// Not needed
			}

			@Override
			public void partClosed(IWorkbenchPartReference partRef) {
				// Not needed
			}

			@Override
			public void partDeactivated(IWorkbenchPartReference partRef) {
				// Not needed
			}

			@Override
			public void partOpened(IWorkbenchPartReference partRef) {
				if (partRef.getPart(false) instanceof IEditorPart) {
					// Update the view when an editor is opened
					// Ensure UI updates happen on the UI thread
					getSite().getShell().getDisplay().asyncExec(() -> updateViewFromActiveEditor());
				}
			}

			@Override
			public void partHidden(IWorkbenchPartReference partRef) {
				// Not needed
			}

			@Override
			public void partVisible(IWorkbenchPartReference partRef) {
				// Not needed
			}

			@Override
			public void partInputChanged(IWorkbenchPartReference partRef) {
				if (partRef.getPart(false) instanceof IEditorPart) {
					// Update the view when editor input changes
					// Ensure UI updates happen on the UI thread
					getSite().getShell().getDisplay().asyncExec(() -> updateViewFromActiveEditor());
				}
			}
		};

		getSite().getPage().addPartListener(partListener);
	}

	/**
	 * Updates the view content based on the currently active editor.
	 */
	private void updateViewFromActiveEditor() {
		IEditorPart editor = getSite().getPage().getActiveEditor();
		if (editor == null) {
			return;
		}
		
		IEditorInput input = editor.getEditorInput();
		if (input == null) {
			return;
		}

		IJavaElement javaElement = input.getAdapter(IJavaElement.class);
		if (javaElement != null) {
			IResource correspondingResource = javaElement.getResource();
			if (correspondingResource != null) {
				setSingleInput(correspondingResource);
			}
		}
	}

	@Override
	public void dispose() {
		// Remove part listener when view is disposed
		if (partListener != null) {
			// Check if page is still available to prevent errors during workbench shutdown
			try {
				IWorkbenchPage page = getSite().getPage();
				if (page != null) {
					page.removePartListener(partListener);
				}
			} catch (Exception e) {
				// Ignore errors during shutdown
			}
			partListener = null;
		}
		super.dispose();
	}

}