/******************************************************************************* * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * *******************************************************************************/ package com.liferay.ide.service.ui.editor; import com.liferay.ide.core.util.CoreUtil; import com.liferay.ide.service.core.util.ServiceUtil; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICodeAssist; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.internal.ui.actions.SelectionConverter; import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.text.JavaWordFinder; import org.eclipse.jdt.ui.actions.SelectionDispatchAction; import org.eclipse.jface.action.IAction; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; /** * @author Gregory Amerson * */ @SuppressWarnings( "restriction" ) public class ServiceMethodHyperlinkDetector extends AbstractHyperlinkDetector { private static class IMethodWrapper { private final boolean base; private final IMethod method; public IMethodWrapper( IMethod method, boolean base ) { this.method = method; this.base = base; } } private static class WrapperMethodCollector extends SearchRequestor { private final List<IMethod> results; private final IMethod method; public WrapperMethodCollector( List<IMethod> results, IMethod method ) { super(); this.results = results; this.method = method; } @Override public void acceptSearchMatch( SearchMatch match ) throws CoreException { final Object element = match.getElement(); if( element instanceof IMethod && matches( (IMethod) element ) ) { this.results.add( (IMethod) element ); } } private boolean matches( IMethod element ) throws JavaModelException { boolean matches = false; if( this.method.getNumberOfParameters() == element.getNumberOfParameters() ) { matches = true; for( int i = 0; i < this.method.getTypeParameters().length; i++ ) { if( ! this.method.getParameterTypes()[i].equals( element.getParameterTypes()[i] ) ) { matches = false; break; } } } return matches; } } private IJavaElement[] lastElements; private ITypeRoot lastInput; private long lastModStamp; private IRegion lastWordRegion; private void addHyperlinks( final List<IHyperlink> links, final IRegion word, final SelectionDispatchAction openAction, final IMethod method, final boolean qualify, final JavaEditor editor ) { if( shouldAddServiceHyperlink( editor ) ) { final IMethod implMethod = getServiceImplMethod( method ); if( implMethod != null ) { links.add( new ServiceMethodImplementationHyperlink( word, openAction, implMethod, qualify ) ); } final IMethodWrapper wrapperMethod = getServiceWrapperMethod( method ); if( wrapperMethod != null ) { if( wrapperMethod.base ) { links.add( new ServiceMethodWrapperLookupHyperlink( editor, word, openAction, wrapperMethod.method, qualify ) ); } else { links.add( new ServiceMethodWrapperHyperlink( word, openAction, wrapperMethod.method, qualify ) ); } } } } public IHyperlink[] detectHyperlinks( ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks ) { IHyperlink[] retval = null; final ITextEditor textEditor = (ITextEditor) getAdapter( ITextEditor.class ); if( textEditor == null ) { return retval; } final ITypeRoot input = EditorUtility.getEditorInputJavaElement( textEditor, false ); final IAction openAction = textEditor.getAction( "OpenEditor" ); if( shouldDetectHyperlinks( textEditor, input, openAction, region ) ) { final IDocumentProvider documentProvider = textEditor.getDocumentProvider(); final IEditorInput editorInput = textEditor.getEditorInput(); final IDocument document = documentProvider.getDocument( editorInput ); final int offset = region.getOffset(); final IRegion wordRegion = JavaWordFinder.findWord( document, offset ); if( isRegionValid( document, wordRegion ) ) { IJavaElement[] elements = new IJavaElement[0]; final long modStamp = documentProvider.getModificationStamp( editorInput ); if( input.equals( this.lastInput ) && modStamp == this.lastModStamp && wordRegion.equals( this.lastWordRegion ) ) { elements = this.lastElements; } else { try { elements = ( (ICodeAssist) input ).codeSelect( wordRegion.getOffset(), wordRegion.getLength() ); elements = selectOpenableElements( elements ); this.lastInput = input; this.lastModStamp = modStamp; this.lastWordRegion = wordRegion; this.lastElements = elements; } catch( JavaModelException e ) { } } if( elements.length != 0 ) { final List<IHyperlink> links = new ArrayList<IHyperlink>( elements.length ); for( IJavaElement element : elements ) { if( element instanceof IMethod ) { addHyperlinks( links, wordRegion, (SelectionDispatchAction) openAction, (IMethod) element, elements.length > 1, (JavaEditor) textEditor ); } } if( links.size() != 0 ) { if( canShowMultipleHyperlinks ) { retval = links.toArray( new IHyperlink[0] ); } else { retval = new IHyperlink[] { links.get( 0 ) }; } } } } } return retval; } @Override public void dispose() { super.dispose(); this.lastElements = null; this.lastInput = null; this.lastWordRegion = null; } private IType findType( IJavaElement parent, String fullyQualifiedName ) throws JavaModelException { IType retval = parent.getJavaProject().findType( fullyQualifiedName ); if( retval == null ) { final IJavaProject[] serviceProjects = ServiceUtil.getAllServiceProjects(); for( final IJavaProject sp : serviceProjects ) { try { retval = sp.findType( fullyQualifiedName ); } catch( Exception e ) { } if( retval != null ) { break; } } } return retval; } private IMethod getServiceImplMethod( final IMethod method ) { IMethod retval = null; try { final IJavaElement methodClass = method.getParent(); final IType methodClassType = method.getDeclaringType(); final String methodClassName = methodClass.getElementName(); if( methodClassName.endsWith( "Util" ) && JdtFlags.isPublic( method ) && JdtFlags.isStatic( method ) ) { final String packageName = methodClassType.getPackageFragment().getElementName(); final String baseServiceName = methodClassName.substring( 0, methodClassName.length() - 4 ); // as per liferay standard real implementation will be in impl package and Impl suffix // e.g. com.example.service.FooUtil.getBar() --> com.example.service.impl.FooImpl.getBar() final String fullyQualifiedName = packageName + ".impl." + baseServiceName + "Impl"; final IType implType = findType( methodClass, fullyQualifiedName ); if( implType != null ) { IMethod[] methods = implType.findMethods( method ); if( CoreUtil.isNullOrEmpty( methods ) ) { final ITypeHierarchy hierarchy = implType.newSupertypeHierarchy( new NullProgressMonitor() ); IType currentType = implType; while( retval == null && currentType != null ) { methods = currentType.findMethods( method );// match name and arguments if( ! CoreUtil.isNullOrEmpty( methods ) ) { retval = methods[0]; } else { currentType = hierarchy.getSuperclass( currentType ); } } } else { retval = methods[0]; } } } } catch( Exception e ) { } return retval; } private IMethodWrapper getServiceWrapperMethod( final IMethod method ) { IMethodWrapper retval = null; try { final IJavaElement methodClass = method.getParent(); final IType methodClassType = method.getDeclaringType(); final String methodClassName = methodClass.getElementName(); if( methodClassName.endsWith( "Util" ) && JdtFlags.isPublic( method ) && JdtFlags.isStatic( method ) ) { final String packageName = methodClassType.getPackageFragment().getElementName(); final String baseServiceName = methodClassName.substring( 0, methodClassName.length() - 4 ); // as per liferay standard wrapper type will be in service package with Wrapper suffix // e.g. com.example.service.FooUtil.getBar() --> com.example.service.FooWrapper.getBar() final String fullyQualifiedName = packageName + "." + baseServiceName + "Wrapper"; final IType wrapperType = findType( methodClass, fullyQualifiedName ); if( wrapperType != null ) { IMethod[] wrapperBaseMethods = wrapperType.findMethods( method ); if( ! CoreUtil.isNullOrEmpty( wrapperBaseMethods ) ) { // look for classes that implement this wrapper final List<IMethod> overrides = new ArrayList<IMethod>(); final SearchRequestor requestor = new WrapperMethodCollector( overrides, method ); final IJavaSearchScope scope = SearchEngine.createStrictHierarchyScope( null, wrapperType, true, false, null ); final SearchPattern search = SearchPattern.createPattern( method.getElementName(), IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE ); new SearchEngine().search( search, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope, requestor, new NullProgressMonitor() ); if( overrides.size() > 1 ) { retval = new IMethodWrapper( wrapperBaseMethods[0], true ); } else if( overrides.size() == 1 ) { retval = new IMethodWrapper( overrides.get( 0 ), false ); } } } } } catch( Exception e ) { } return retval; } private boolean isInheritDoc( IDocument document, IRegion wordRegion ) { try { String word = document.get( wordRegion.getOffset(), wordRegion.getLength() ); return "inheritDoc".equals( word ); } catch( BadLocationException e ) { return false; } } private boolean isRegionValid( IDocument document, IRegion wordRegion ) { if( wordRegion != null && wordRegion.getLength() != 0 && ( !isInheritDoc( document, wordRegion ) ) ) { return true; } return false; } private IJavaElement[] selectOpenableElements( IJavaElement[] elements ) { final List<IJavaElement> result = new ArrayList<IJavaElement>( elements.length ); for( int i = 0; i < elements.length; i++ ) { final IJavaElement element = elements[i]; switch( element.getElementType() ) { case IJavaElement.PACKAGE_DECLARATION: case IJavaElement.PACKAGE_FRAGMENT: case IJavaElement.PACKAGE_FRAGMENT_ROOT: case IJavaElement.JAVA_PROJECT: case IJavaElement.JAVA_MODEL: break; default: result.add( element ); break; } } return result.toArray( new IJavaElement[result.size()] ); } private boolean shouldAddServiceHyperlink( final JavaEditor editor ) { return SelectionConverter.canOperateOn( editor ); } private boolean shouldDetectHyperlinks( final ITextEditor textEditor, final ITypeRoot input, final IAction openAction, final IRegion region ) { return region != null && textEditor instanceof JavaEditor && openAction instanceof SelectionDispatchAction && input != null; } }