/*******************************************************************************
* Copyright (c) 2016 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.metadata;
import java.time.Duration;
import java.util.Map;
import java.util.function.Function;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
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.SearchPattern;
import org.springframework.ide.eclipse.boot.properties.editor.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.eclipse.boot.properties.editor.util.LimitedTimeCache;
import org.springframework.ide.eclipse.boot.util.Log;
import org.springframework.ide.eclipse.editor.support.util.StringUtil;
import org.springsource.ide.eclipse.commons.frameworks.core.async.FluxJdtSearch;
import reactor.core.publisher.Mono;
/**
* Provides the algorithm for 'class-reference' valueProvider.
* <p>
* See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc
*
* @author Kris De Volder
*/
public class ClassReferenceProvider extends JdtSearchingValueProvider {
/**
* Default value for the 'concrete' parameter.
*/
private static final boolean DEFAULT_CONCRETE = true;
private static final ClassReferenceProvider UNTARGETTED_INSTANCE = new ClassReferenceProvider(null, DEFAULT_CONCRETE);
public static final Function<Map<String, Object>, ValueProviderStrategy> FACTORY = LimitedTimeCache.applyOn(
Duration.ofMinutes(1),
(params) -> {
String target = getTarget(params);
Boolean concrete = getConcrete(params);
if (target!=null || concrete!=null) {
if (concrete==null) {
concrete = DEFAULT_CONCRETE;
}
return new ClassReferenceProvider(target, concrete);
}
return UNTARGETTED_INSTANCE;
}
);
private static String getTarget(Map<String, Object> params) {
if (params!=null) {
Object obj = params.get("target");
if (obj instanceof String) {
String target = (String) obj;
if (StringUtil.hasText(target)) {
return target;
}
}
}
return null;
}
/**
* Filter that drops all search matches that are not concrete types.
*/
private Mono<StsValueHint> filterConcreteTypes(SearchMatch match) {
Object element = match.getElement();
if (element instanceof IType) {
IType type = (IType) element;
if (isAbstract(type)) {
return Mono.empty();
}
return Mono.justOrEmpty(hint(type));
}
return Mono.empty();
}
private static boolean isAbstract(IType type) {
try {
return type.isInterface() || Flags.isAbstract(type.getFlags());
} catch (Exception e) {
Log.log(e);
return false;
}
}
@Override
protected Function<SearchMatch, Mono<StsValueHint>> getPostProcessor() {
if (concrete) {
return this::filterConcreteTypes;
}
return super.getPostProcessor();
}
private static Boolean getConcrete(Map<String, Object> params) {
try {
if (params!=null) {
Object obj = params.get("concrete");
if (obj instanceof String) {
String concrete = (String) obj;
return Boolean.valueOf(concrete);
} else if (obj instanceof Boolean) {
return (Boolean) obj;
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
/**
* Optional, fully qualified name of the 'target' type. Suggested hints should be a subtype of this type.
*/
private String target;
/**
* Optional parameter, whether only concrete types should be suggested. Default value is true.
*/
private boolean concrete;
private ClassReferenceProvider(String target, boolean concrete) {
this.target = target;
this.concrete = concrete;
}
@Override
protected SearchPattern toPattern(String query) {
String wildcardedQuery = toWildCardPattern(query);
// Beware: the commented code may seem like a good idea, but its not, because it drops 'enums' from the search results.
// So... for example org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy will not be found as a
// a concrete mongodb FieldNamingStrategy implementation!
// if (concrete) {
// return toClassPattern(wildcardedQuery);
// }
return toTypePattern(wildcardedQuery);
}
public IJavaSearchScope getScope(IJavaProject project) throws JavaModelException {
if (target!=null) {
IType type = getTargetType(project);
if (type!=null) {
boolean onlySubtypes = true;
boolean includeFocusType = true;
WorkingCopyOwner owner = null;
return SearchEngine.createStrictHierarchyScope(project, type, onlySubtypes, includeFocusType, owner);
}
return null; //target type not on classpath so... can't search (and arguably if type isn't on CP
// neither should any of its subtypes... so searching is a bit pointless).
// scope = null will cause FluxJdtSearch to quickly return zero results.
}
return FluxJdtSearch.searchScope(project);
}
private IType getTargetType(IJavaProject project) {
try {
if (target!=null) {
return project.findType(target);
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
}