/******************************************************************************* * Copyright (c) 2014 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.properties.editor.completions; import java.util.ArrayList; import java.util.Collections; import java.util.List; 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.JavaModelException; import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; import org.springframework.ide.eclipse.boot.properties.editor.SpringPropertiesCompletionEngine; import org.springframework.ide.eclipse.boot.properties.editor.SpringPropertiesEditorPlugin; import org.springframework.ide.eclipse.boot.properties.editor.metadata.PropertyInfo; import org.springframework.ide.eclipse.boot.properties.editor.metadata.PropertyInfo.PropertySource; import org.springframework.ide.eclipse.editor.support.util.HtmlSnippet; import org.springframework.ide.eclipse.editor.support.util.StringUtil; /** * Information object that is displayed in SpringPropertiesTextHover's information * control. * <p> * Essentially this is a wrapper around {@link ConfigurationMetadataProperty} * * @author Kris De Volder */ public class SpringPropertyHoverInfo extends AbstractPropertyHoverInfo { private static final String[] NO_ARGS = new String[0]; /** * Java project which is used to find declaration for 'navigate to declaration' action */ private IJavaProject javaProject; /** * Data object to display in 'hover text' */ private PropertyInfo data; public SpringPropertyHoverInfo(IJavaProject project, PropertyInfo data) { this.javaProject = project; this.data = data; } public PropertyInfo getElement() { return data; } public boolean canOpenDeclaration() { return getJavaElements()!=null; } /** * Like 'getSources' but converts raw info into IJavaElements. Raw data which fails to be converted * is silenetly ignored. */ public List<IJavaElement> getJavaElements() { try { if (javaProject!=null) { SpringPropertiesCompletionEngine.debug("javaProject = "+javaProject.getElementName()); List<PropertySource> sources = getSources(); SpringPropertiesCompletionEngine.debug("propertySources = "+sources); if (!sources.isEmpty()) { ArrayList<IJavaElement> elements = new ArrayList<IJavaElement>(); for (PropertySource source : sources) { String typeName = source.getSourceType(); if (typeName!=null) { IType type = javaProject.findType(typeName); IMethod method = null; if (type!=null) { String methodSig = source.getSourceMethod(); if (methodSig!=null) { method = getMethod(type, methodSig); } else { method = getSetter(type, getElement()); } } if (method!=null) { elements.add(method); } else if (type!=null) { elements.add(type); } } } return elements; } } else { SpringPropertiesCompletionEngine.debug("javaProject = null"); } } catch (Exception e) { SpringPropertiesEditorPlugin.log(e); } return Collections.emptyList(); } /** * Attempt to find corresponding setter method for a given property. * @return setter method, or null if not found. */ private IMethod getSetter(IType type, PropertyInfo propertyInfo) { try { String propName = propertyInfo.getName(); String setterName = "set" +Character.toUpperCase(propName.charAt(0)) +toCamelCase(propName.substring(1)); String sloppySetterName = setterName.toLowerCase(); IMethod sloppyMatch = null; for (IMethod m : type.getMethods()) { String mname = m.getElementName(); if (setterName.equals(mname)) { //found 'exact' name match... done return m; } else if (mname.toLowerCase().equals(sloppySetterName)) { sloppyMatch = m; } } return sloppyMatch; } catch (Exception e) { SpringPropertiesEditorPlugin.log(e); return null; } } /** * Convert hyphened name to camel case name. It is * safe to call this on an already camel-cased name. */ private String toCamelCase(String name) { if (name.isEmpty()) { return name; } else { StringBuilder camel = new StringBuilder(); char[] chars = name.toCharArray(); for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (c=='-') { i++; if (i<chars.length) { camel.append(Character.toUpperCase(chars[i])); } } else { camel.append(chars[i]); } } return camel.toString(); } } /** * Get 'raw' info about sources that define this property. */ public List<PropertySource> getSources() { return data.getSources(); } private IMethod getMethod(IType type, String methodSig) throws JavaModelException { int nameEnd = methodSig.indexOf('('); String name; if (nameEnd>=0) { name = methodSig.substring(0, nameEnd); } else { name = methodSig; } //TODO: This code assumes 0 arguments, which is the case currently for all // 'real' data in spring jars. IMethod m = type.getMethod(name, NO_ARGS); if (m!=null) { return m; } //try find a method with the same name. for (IMethod meth : type.getMethods()) { if (name.equals(meth.getElementName())) { return meth; } } return null; } @Override protected Object getDefaultValue() { return data.getDefaultValue(); } @Override protected IJavaProject getJavaProject() { return javaProject; } @Override protected HtmlSnippet getDescription() { String desc = data.getDescription(); if (StringUtil.hasText(desc)) { return HtmlSnippet.text(desc); } return null; } @Override protected String getType() { return data.getType(); } @Override protected String getDeprecationReason() { return data.getDeprecationReason(); } @Override protected String getId() { return data.getId(); } @Override protected String getDeprecationReplacement() { return data.getDeprecationReplacement(); } @Override protected boolean isDeprecated() { return data.isDeprecated(); } }