Javafx: Validate property tags. Check that value provided by property's nested tag(s) is compatible with or convertible to the property type. Show warning for conversion of unrelated types: technically it might work, but usually not in the way the user expects. Validate enum constants provided in fx:value attribute. Tests added. (IDEA-103596)

This commit is contained in:
Pavel Dolgov
2016-03-25 15:10:09 +03:00
parent a45a309060
commit 413c43eb5f
15 changed files with 384 additions and 84 deletions
@@ -236,7 +236,7 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase {
myFixture.addClass("package p;\n" +
"public class Root extends javafx.scene.layout.GridPane{\n" +
" public Root(@javafx.beans.NamedArg(\"axis\") javafx.scene.Node node ) {\n" +
" super(node)\n" +
" super(node);\n" +
" }\n" +
" public javafx.beans.property.Property<javafx.scene.Node> axis;" +
" public void setAxis() {}" +
@@ -346,16 +346,40 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase {
doTest();
}
public void testPropertyTagCompatibleClass() throws Exception {
doTest();
}
public void testPropertyTagCompatiblePrimitive() throws Exception {
doTest();
}
public void testPropertyTagIncompatibleClass() throws Exception {
doTest();
}
public void testPropertyTagIncompatiblePrimitive() throws Exception {
doTest();
}
public void testPropertyTagUnrelatedClass() throws Exception {
doWarningsTest();
}
public void testPropertyTagUnrelatedPrimitive() throws Exception {
doWarningsTest();
}
public void testCharsetInInclude() throws Exception {
myFixture.addFileToProject("sample.fxml", "<?import javafx.scene.layout.GridPane?>\n" +
"<fx:root type=\"javafx.scene.layout.GridPane\" xmlns:fx=\"http://javafx.com/fxml\"/>\n");
myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml");
doWarningsTest();
}
public void testIncludedForm() throws Exception {
myFixture.addFileToProject("sample.fxml", "<?import javafx.scene.layout.GridPane?>\n" +
"<fx:root type=\"javafx.scene.layout.GridPane\" xmlns:fx=\"http://javafx.com/fxml\"/>\n");
myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml");
doWarningsTest();
}
public void testInjectedControllerFields() throws Exception {
@@ -372,6 +396,10 @@ public class JavaFXHighlightingTest extends AbstractJavaFXTestCase {
myFixture.testHighlighting(false, false, false, getTestName(true) + ".fxml");
}
private void doWarningsTest() {
myFixture.testHighlighting(true, false, false, getTestName(true) + ".fxml");
}
@Override
protected void enableInspections() {
myFixture.enableInspections(new XmlPathReferenceInspection(),
@@ -16,6 +16,7 @@
package org.jetbrains.plugins.javaFX.fxml;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.lang.ASTNode;
@@ -36,6 +37,8 @@ import com.intellij.util.Processor;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxClassBackedElementDescriptor;
@@ -43,6 +46,9 @@ import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxDefaultPropertyElemen
import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxPropertyElementDescriptor;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* User: anna
@@ -307,9 +313,18 @@ public class JavaFxPsiUtil {
return null;
}
public static PsiClass getTagClass(XmlTag xmlTag) {
@Nullable
public static PsiClass getTagClass(@NotNull XmlTag xmlTag) {
final XmlElementDescriptor descriptor = xmlTag.getDescriptor();
if (descriptor != null) {
if (descriptor instanceof JavaFxDefaultPropertyElementDescriptor) {
final JavaFxClassBackedElementDescriptor rootTagDescriptor =
((JavaFxDefaultPropertyElementDescriptor)descriptor).getFxRootTagDescriptor(xmlTag);
if (rootTagDescriptor != null) {
final PsiElement declaration = rootTagDescriptor.getDeclaration();
return declaration instanceof PsiClass ? (PsiClass)declaration : null;
}
}
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
return (PsiClass)declaration;
@@ -318,20 +333,27 @@ public class JavaFxPsiUtil {
return null;
}
public static boolean isVisibleInFxml(PsiMember psiMember) {
public static boolean isVisibleInFxml(@NotNull PsiMember psiMember) {
return psiMember.hasModifierProperty(PsiModifier.PUBLIC) ||
AnnotationUtil.isAnnotated(psiMember, JavaFxCommonNames.JAVAFX_FXML_ANNOTATION, false);
}
public static PsiMethod findValueOfMethod(@NotNull final PsiClass tagClass) {
final PsiMethod[] methods = tagClass.findMethodsByName(JavaFxCommonNames.VALUE_OF, false);
@Nullable
public static PsiMethod findValueOfMethod(@NotNull final PsiType psiType) {
final PsiClass psiClass = PsiTypesUtil.getPsiClass(psiType);
return psiClass != null ? findValueOfMethod(psiClass) : null;
}
@Nullable
public static PsiMethod findValueOfMethod(@NotNull final PsiClass psiClass) {
final PsiMethod[] methods = psiClass.findMethodsByName(JavaFxCommonNames.VALUE_OF, true);
for (PsiMethod method : methods) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 1) {
final PsiType type = parameters[0].getType();
if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING) || type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
if (method.hasModifierProperty(PsiModifier.STATIC) && tagClass.equals(PsiUtil.resolveClassInType(method.getReturnType()))) {
if (psiClass.equals(PsiUtil.resolveClassInType(method.getReturnType()))) {
return method;
}
}
@@ -407,11 +429,16 @@ public class JavaFxPsiUtil {
return null;
}
public static String isAbleToInstantiate(final PsiClass psiClass) {
public static boolean isAbleToInstantiate(final PsiClass psiClass) {
return isAbleToInstantiate(psiClass, message -> {
});
}
public static boolean isAbleToInstantiate(final PsiClass psiClass, @NotNull Consumer<String> messageConsumer) {
if (psiClass.getConstructors().length > 0) {
for (PsiMethod constr : psiClass.getConstructors()) {
final PsiParameter[] parameters = constr.getParameterList().getParameters();
if (parameters.length == 0) return null;
for (PsiMethod constructor : psiClass.getConstructors()) {
final PsiParameter[] parameters = constructor.getParameterList().getParameters();
if (parameters.length == 0) return true;
boolean annotated = true;
for (PsiParameter parameter : parameters) {
if (!AnnotationUtil.isAnnotated(parameter, JavaFxCommonNames.JAVAFX_BEANS_NAMED_ARG, false)) {
@@ -419,14 +446,17 @@ public class JavaFxPsiUtil {
break;
}
}
if (annotated) return null;
if (annotated) return true;
}
final PsiMethod valueOf = findValueOfMethod(psiClass);
if (valueOf == null) {
if (!hasBuilder(psiClass)) return "Unable to instantiate";
if (!hasBuilder(psiClass)) {
messageConsumer.accept("Unable to instantiate");
return false;
}
}
}
return null;
return true;
}
public static boolean hasBuilder(@NotNull final PsiClass psiClass) {
@@ -460,57 +490,187 @@ public class JavaFxPsiUtil {
});
}
public static String isClassAcceptable(@Nullable XmlTag parentTag, @Nullable final PsiClass aClass) {
if (parentTag == null) {
return null;
}
if (aClass != null && aClass.isValid()) {
XmlElementDescriptor descriptor = parentTag.getDescriptor();
if (descriptor instanceof JavaFxDefaultPropertyElementDescriptor) {
descriptor = ((JavaFxDefaultPropertyElementDescriptor)descriptor).getFxRootTagDescriptor(parentTag);
}
public static boolean isClassAcceptable(@Nullable XmlTag targetTag, @Nullable final PsiClass fromClass) {
return isClassAcceptable(targetTag, fromClass, (message, type) -> {
});
}
if (descriptor instanceof JavaFxPropertyElementDescriptor) {
final PsiClass containingClass = ((JavaFxPropertyElementDescriptor)descriptor).getPsiClass();
final PsiElement declaration = descriptor.getDeclaration();
final PsiType propertyType = getWritablePropertyType(containingClass, declaration);
return canCoerce(aClass, propertyType);
}
else if (descriptor instanceof JavaFxClassBackedElementDescriptor) {
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
final PsiType type = getDefaultPropertyExpectedType((PsiClass)declaration);
if (type != null) {
return canCoerce(aClass, type);
public static boolean isClassAcceptable(@Nullable XmlTag targetTag, @Nullable final PsiClass fromClass,
@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
if (targetTag == null || fromClass == null || !fromClass.isValid()) {
return true;
}
XmlElementDescriptor tagDescriptor = targetTag.getDescriptor();
if (tagDescriptor instanceof JavaFxDefaultPropertyElementDescriptor) {
tagDescriptor = ((JavaFxDefaultPropertyElementDescriptor)tagDescriptor).getFxRootTagDescriptor(targetTag);
}
if (tagDescriptor instanceof JavaFxPropertyElementDescriptor) {
final PsiClass containingClass = ((JavaFxPropertyElementDescriptor)tagDescriptor).getPsiClass();
final PsiType targetType = getWritablePropertyType(containingClass, tagDescriptor.getDeclaration());
return canCoerce(targetType, fromClass, targetTag, messageConsumer);
}
else if (tagDescriptor instanceof JavaFxClassBackedElementDescriptor) {
final PsiElement tagDeclaration = tagDescriptor.getDeclaration();
if (tagDeclaration instanceof PsiClass) {
PsiClass defaultPropertyOwnerClass = (PsiClass)tagDeclaration;
final XmlAttribute factoryAttr = targetTag.getAttribute(FxmlConstants.FX_FACTORY);
if (factoryAttr != null) {
final PsiClass factoryReturnClass = getFactoryProducedClass((PsiClass)tagDeclaration, factoryAttr.getValue());
if (factoryReturnClass == null) {
return true;
}
defaultPropertyOwnerClass = factoryReturnClass;
}
final PsiType targetType = getDefaultPropertyExpectedType(defaultPropertyOwnerClass);
if (targetType != null) {
return canCoerce(targetType, fromClass, targetTag, messageConsumer);
}
if (!isObservableCollection(defaultPropertyOwnerClass)) {
return noDefaultPropertyError(messageConsumer);
}
}
}
return null;
return true;
}
@Nullable
private static String canCoerce(@NotNull PsiClass aClass, @Nullable PsiType type) {
PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(type, aClass.getResolveScope());
if (collectionItemType == null && InheritanceUtil.isInheritor(type, JavaFxCommonNames.JAVAFX_BEANS_PROPERTY)) {
collectionItemType = getWritablePropertyType(type, aClass.getProject());
private static boolean noDefaultPropertyError(@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
messageConsumer.accept("Parent tag has no default property",
Validator.ValidationHost.ErrorType.ERROR);
return false;
}
private static boolean canCoerce(@Nullable PsiType targetType, @NotNull PsiClass fromClass, @NotNull PsiElement context,
@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
if (targetType == null) return true;
targetType = eraseFreeTypeParameters(targetType);
PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(targetType, fromClass.getResolveScope());
if (collectionItemType == null && InheritanceUtil.isInheritor(targetType, JavaFxCommonNames.JAVAFX_BEANS_PROPERTY)) {
collectionItemType = getWritablePropertyType(targetType, fromClass.getProject());
}
if (collectionItemType != null && PsiPrimitiveType.getUnboxedType(collectionItemType) == null) {
final PsiClass baseClass = PsiUtil.resolveClassInType(collectionItemType);
if (baseClass != null) {
final String qualifiedName = baseClass.getQualifiedName();
if (qualifiedName != null && !Comparing.strEqual(qualifiedName, CommonClassNames.JAVA_LANG_STRING)) {
if (!InheritanceUtil.isInheritor(aClass, qualifiedName)) {
return unableToCoerceMessage(aClass, qualifiedName);
}
if (collectionItemType != null) {
return canCoerceImpl(collectionItemType, fromClass, context, messageConsumer);
}
return canCoerceImpl(targetType, fromClass, context, messageConsumer);
}
/**
* Similar to {@link GenericsUtil#getVariableTypeByExpressionType(PsiType)} and {@link TypeConversionUtil#erasure(PsiType)}
*/
@NotNull
public static PsiType eraseFreeTypeParameters(@NotNull PsiType psiType) {
return psiType.accept(new PsiTypeVisitor<PsiType>() {
@Nullable
@Override
public PsiType visitType(PsiType type) {
return type;
}
@Nullable
@Override
public PsiType visitClassType(PsiClassType classType) {
final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics();
final PsiClass aClass = resolveResult.getElement();
if (aClass == null) return classType;
if (aClass instanceof PsiTypeParameter) return null;
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
boolean unchanged = true;
for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(aClass)) {
final PsiType typeArgument = resolveResult.getSubstitutor().substitute(typeParameter);
if (typeArgument == null) return classType.rawType();
final PsiType toPut = typeArgument.accept(this);
if (toPut == null) return classType.rawType();
unchanged &= toPut == typeArgument;
substitutor = substitutor.put(typeParameter, toPut);
}
if (unchanged) return classType;
final PsiManager manager = aClass.getManager();
final PsiAnnotation[] applicableAnnotations = classType.getApplicableAnnotations();
return JavaPsiFacade.getInstance(manager.getProject()).getElementFactory()
.createType(aClass, substitutor, PsiUtil.getLanguageLevel(aClass), applicableAnnotations);
}
@Override
public PsiType visitWildcardType(PsiWildcardType wildcardType) {
final PsiType bound = wildcardType.getBound();
return bound != null ? bound.accept(this) : null;
}
@Nullable
@Override
public PsiType visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
return capturedWildcardType.getUpperBound().accept(this);
}
@Override
public PsiType visitEllipsisType(PsiEllipsisType ellipsisType) {
return visitArrayType(ellipsisType);
}
@Override
public PsiType visitArrayType(PsiArrayType arrayType) {
final PsiType componentType = arrayType.getComponentType();
final PsiType newComponentType = componentType.accept(this);
if (newComponentType == componentType) return arrayType;
return newComponentType != null ? newComponentType.createArrayType() : null;
}
@Override
public PsiType visitDisjunctionType(PsiDisjunctionType disjunctionType) {
final PsiClassType lub = PsiTypesUtil.getLowestUpperBoundClassType(disjunctionType);
return lub != null ? lub.accept(this) : null;
}
});
}
private static boolean canCoerceImpl(@NotNull PsiType targetType, @NotNull PsiClass fromClass, @NotNull PsiElement context,
@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
if (targetType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) ||
targetType.equalsToText(CommonClassNames.JAVA_LANG_STRING) ||
targetType.isAssignableFrom(PsiTypesUtil.getClassType(fromClass))) {
return true;
}
final PsiClassType boxedTargetClass =
targetType instanceof PsiPrimitiveType ? ((PsiPrimitiveType)targetType).getBoxedType(context) : null;
if (boxedTargetClass != null && InheritanceUtil.isInheritor(boxedTargetClass, CommonClassNames.JAVA_LANG_NUMBER) ||
InheritanceUtil.isInheritor(targetType, CommonClassNames.JAVA_LANG_NUMBER)) {
if (Comparing.strEqual(fromClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING) ||
InheritanceUtil.isInheritor(fromClass, CommonClassNames.JAVA_LANG_NUMBER)) {
return true;
}
return unrelatedTypesWarning(targetType, fromClass, messageConsumer);
}
final PsiMethod valueOfMethod = findValueOfMethod(targetType);
final PsiType valueOfParameterType = valueOfMethod != null && valueOfMethod.getParameterList().getParametersCount() == 1 ?
valueOfMethod.getParameterList().getParameters()[0].getType() : null;
if (valueOfParameterType != null && valueOfParameterType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
return true;
}
if (Comparing.strEqual(fromClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING)) {
if (isPrimitiveOrBoxed(targetType) ||
valueOfParameterType != null && valueOfParameterType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
return true;
}
}
return null;
if (valueOfMethod != null) {
return unrelatedTypesWarning(targetType, fromClass, messageConsumer);
}
return unableToCoerceError(targetType, fromClass, messageConsumer);
}
private static String unableToCoerceMessage(PsiClass aClass, String qualifiedName) {
return "Unable to coerce " + HighlightUtil.formatClass(aClass) + " to " + qualifiedName;
private static boolean unableToCoerceError(@NotNull PsiType targetType, @NotNull PsiClass fromClass,
@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
messageConsumer.accept("Unable to coerce " + HighlightUtil.formatClass(fromClass) + " to " + targetType.getCanonicalText(),
Validator.ValidationHost.ErrorType.ERROR);
return false;
}
private static boolean unrelatedTypesWarning(@NotNull PsiType targetType, @NotNull PsiClass fromClass,
@NotNull BiConsumer<? super String, Validator.ValidationHost.ErrorType> messageConsumer) {
messageConsumer.accept("Conversion between unrelated types, " + HighlightUtil.formatClass(fromClass) +
" to " + targetType.getCanonicalText(),
Validator.ValidationHost.ErrorType.WARNING);
return true;
}
public static boolean isOutOfHierarchy(final XmlAttributeValue element) {
@@ -667,7 +827,7 @@ public class JavaFxPsiUtil {
}
@Nullable
public static PsiClass getPropertyClass(PsiType propertyType, XmlAttributeValue context) {
public static PsiClass getPropertyClass(PsiType propertyType, PsiElement context) {
if (propertyType instanceof PsiPrimitiveType) {
PsiClassType boxedType = ((PsiPrimitiveType)propertyType).getBoxedType(context);
return boxedType != null ? boxedType.resolve() : null;
@@ -818,13 +978,17 @@ public class JavaFxPsiUtil {
}
private static boolean isWritablePropertyType(@NotNull PsiClass psiClass, @NotNull PsiType fieldType) {
return (InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_LIST) ||
InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_SET) ||
InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_ARRAY)) &&
return isObservableCollection(PsiUtil.resolveClassInType(fieldType)) &&
JavaGenericsUtil.getCollectionItemType(fieldType, psiClass.getResolveScope()) != null ||
InheritanceUtil.isInheritor(fieldType, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_MAP);
}
private static boolean isObservableCollection(@Nullable PsiClass psiClass) {
return InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_LIST) ||
InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_SET) ||
InheritanceUtil.isInheritor(psiClass, JavaFxCommonNames.JAVAFX_COLLECTIONS_OBSERVABLE_ARRAY);
}
@Nullable
private static PsiSubstitutor getTagClassSubstitutor(@NotNull XmlAttribute xmlAttribute, @NotNull PsiClass controllerClass) {
final XmlTag xmlTag = xmlAttribute.getParent();
@@ -914,6 +1078,35 @@ public class JavaFxPsiUtil {
return null;
}
@Nullable
public static PsiClass getFactoryProducedClass(@NotNull PsiClass aClass, @Nullable String factoryMethodName) {
if (factoryMethodName == null) return null;
final PsiMethod[] methods = aClass.findMethodsByName(factoryMethodName, true);
for (PsiMethod method : methods) {
if (method.getParameterList().getParametersCount() == 0 &&
method.hasModifierProperty(PsiModifier.STATIC)) {
return PsiUtil.resolveClassInType(method.getReturnType());
}
}
return null;
}
@Nullable
public static String validateEnumConstant(@NotNull PsiClass enumClass, @NonNls @Nullable String name) {
if (!enumClass.isEnum() || name == null) return null;
final Set<String> constantNames = CachedValuesManager.getCachedValue(enumClass, () ->
CachedValueProvider.Result.create(Arrays.stream(enumClass.getFields())
.filter(PsiEnumConstant.class::isInstance)
.map(PsiField::getName)
.map(String::toUpperCase)
.collect(Collectors.toCollection(THashSet::new)),
PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
if (!constantNames.contains(name.toUpperCase())) {
return "No enum constant '" + name + "' in " + enumClass.getQualifiedName();
}
return null;
}
private static class JavaFxControllerCachedValueProvider implements CachedValueProvider<PsiClass> {
private final Project myProject;
private final PsiFile myContainingFile;
@@ -36,7 +36,7 @@ public class JavaFxColorRgbInspection extends XmlSuppressableInspectionTool {
final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
if (descriptor instanceof JavaFxPropertyAttributeDescriptor) {
final PsiClass psiClass = ((JavaFxPropertyAttributeDescriptor)descriptor).getPsiClass();
if (JavaFxCommonNames.JAVAFX_SCENE_COLOR.equals(psiClass.getQualifiedName())) {
if (psiClass != null && JavaFxCommonNames.JAVAFX_SCENE_COLOR.equals(psiClass.getQualifiedName())) {
final XmlAttributeValue valueElement = attribute.getValueElement();
final PsiElement location = valueElement != null ? valueElement : attribute;
validateColorComponent(psiClass, attribute.getName(), attributeValue, location);
@@ -87,7 +87,7 @@ public class JavaFxExpandAttributeIntention extends PsiElementBaseIntentionActio
tagType = PropertyUtil.getPropertyType((PsiMember)declaration);
}
PsiClass tagClass = PsiUtil.resolveClassInType(tagType instanceof PsiPrimitiveType ? ((PsiPrimitiveType)tagType).getBoxedType(parent) : tagType);
if ((tagClass != null && JavaFxPsiUtil.isAbleToInstantiate(tagClass) == null) || descriptor instanceof JavaFxStaticSetterAttributeDescriptor) {
if ((tagClass != null && JavaFxPsiUtil.isAbleToInstantiate(tagClass)) || descriptor instanceof JavaFxStaticSetterAttributeDescriptor) {
setText("Expand '" + ((XmlAttribute)parent).getName() + "' to tag");
return true;
}
@@ -9,7 +9,6 @@ import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
@@ -322,27 +321,14 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor,
} else {
final XmlAttribute factoryAttr = context.getAttribute(FxmlConstants.FX_FACTORY);
if (factoryAttr != null) {
final XmlAttributeValue valueElement = factoryAttr.getValueElement();
if (valueElement != null) {
final PsiReference reference = valueElement.getReference();
final PsiElement staticFactoryMethod = reference != null ? reference.resolve() : null;
if (staticFactoryMethod instanceof PsiMethod &&
((PsiMethod)staticFactoryMethod).getParameterList().getParametersCount() == 0 &&
((PsiMethod)staticFactoryMethod).hasModifierProperty(PsiModifier.STATIC)) {
aClass = PsiUtil.resolveClassInType(((PsiMethod)staticFactoryMethod).getReturnType());
}
}
aClass = JavaFxPsiUtil.getFactoryProducedClass(myPsiClass, factoryAttr.getValue());
}
}
final String canCoerceError = JavaFxPsiUtil.isClassAcceptable(parentTag, aClass);
if (canCoerceError != null) {
host.addMessage(context.getNavigationElement(), canCoerceError, ValidationHost.ErrorType.ERROR);
}
JavaFxPsiUtil.isClassAcceptable(parentTag, aClass, (errorMessage, errorType) ->
host.addMessage(context.getNavigationElement(), errorMessage, errorType));
if (aClass != null && aClass.isValid()) {
final String message = JavaFxPsiUtil.isAbleToInstantiate(aClass);
if (message != null) {
host.addMessage(context, message, ValidationHost.ErrorType.ERROR);
}
JavaFxPsiUtil.isAbleToInstantiate(aClass, errorMessage ->
host.addMessage(context, errorMessage, ValidationHost.ErrorType.ERROR));
}
}
}
@@ -82,6 +82,9 @@ public class JavaFxDefaultPropertyAttributeDescriptor extends JavaFxPropertyAttr
if (FxmlConstants.FX_VALUE.equals(attributeName)) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass((XmlAttributeValue)context);
if (tagClass != null) {
if (tagClass.isEnum()) {
return JavaFxPsiUtil.validateEnumConstant(tagClass, value);
}
final PsiMethod method = JavaFxPsiUtil.findValueOfMethod(tagClass);
if (method == null) {
return "Unable to coerce '" + value + "' to " + tagClass.getQualifiedName() + ".";
@@ -297,10 +297,8 @@ public class JavaFxDefaultPropertyElementDescriptor implements XmlElementDescrip
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
final PsiClass psiClass = (PsiClass)declaration;
final String canCoerceError = JavaFxPsiUtil.isClassAcceptable(context.getParentTag(), psiClass);
if (canCoerceError != null) {
host.addMessage(context.getNavigationElement(), canCoerceError, ValidationHost.ErrorType.ERROR);
}
JavaFxPsiUtil.isClassAcceptable(context.getParentTag(), psiClass, (errorMessage, errorType) ->
host.addMessage(context.getNavigationElement(), errorMessage, errorType));
if (FxmlConstants.FX_COPY.equals(contextName)) {
boolean copyConstructorFound = false;
for (PsiMethod constructor : psiClass.getConstructors()) {
@@ -99,7 +99,7 @@ class JavaFxComponentIdReferenceProvider extends PsiReferenceProvider {
final XmlTag parentTag = currentTag.getParentTag();
for (final String id : fileIds.keySet()) {
final XmlAttributeValue resolvedAttrValue = fileIds.get(id);
if (JavaFxPsiUtil.isClassAcceptable(parentTag, JavaFxPsiUtil.getTagClass(resolvedAttrValue)) == null) {
if (JavaFxPsiUtil.isClassAcceptable(parentTag, JavaFxPsiUtil.getTagClass(resolvedAttrValue))) {
acceptableIds.add(id);
}
}
@@ -66,7 +66,7 @@ abstract class JavaFxImportClassFix extends ImportClassFixBase<XmlTag, JavaFxTag
@Override
protected boolean isAccessible(PsiMember member, XmlTag reference) {
return member instanceof PsiClass && JavaFxPsiUtil.isClassAcceptable(reference.getParentTag(), (PsiClass)member) == null;
return member instanceof PsiClass && JavaFxPsiUtil.isClassAcceptable(reference.getParentTag(), (PsiClass)member);
}
@Override
@@ -0,0 +1,17 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.Color?>
<?import javafx.scene.shape.Rectangle?>
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
<Rectangle width="100" height="100">
<fill>
<Color green="1"/>
</fill>
<VBox.margin>
<Insets top="10"/>
</VBox.margin>
</Rectangle>
<padding>
<Insets left="20"/>
</padding>
</fx:root>
@@ -0,0 +1,22 @@
<?import javafx.scene.layout.Priority?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import java.lang.Double?>
<?import java.lang.Integer?>
<?import java.lang.String?>
<VBox xmlns:fx="http://javafx.com/fxml">
<Rectangle width="100" height="100" fill="darkcyan">
<arcWidth>
<Double fx:value="10"/>
</arcWidth>
<arcHeight>
<Integer fx:value="40"/>
</arcHeight>
<VBox.vgrow>
<Priority fx:value="always"/>
</VBox.vgrow>
<opacity>
<String fx:value="0.5"/>
</opacity>
</Rectangle>
</VBox>
@@ -0,0 +1,15 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.Priority?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.Color?>
<?import javafx.scene.shape.Rectangle?>
<VBox xmlns:fx="http://javafx.com/fxml">
<Rectangle width="100" height="100">
<VBox.margin>
<<error descr="Unable to coerce javafx.scene.paint.Color to javafx.geometry.Insets">Color</error> green="1"/>
</VBox.margin>
</Rectangle>
<padding>
<<error descr="Unable to coerce javafx.scene.layout.Priority to javafx.geometry.Insets">Priority</error> fx:value="always"/>
</padding>
</VBox>
@@ -0,0 +1,14 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import java.lang.Double?>
<?import java.lang.Integer?>
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
<padding>
<<error descr="Unable to coerce java.lang.Integer to javafx.geometry.Insets">Integer</error> fx:value="10"/>
</padding>
<Rectangle width="100" height="100">
<VBox.vgrow>
<<error descr="Unable to coerce java.lang.Double to javafx.scene.layout.Priority">Double</error> fx:value="20"/>
</VBox.vgrow>
</Rectangle>
</fx:root>
@@ -0,0 +1,14 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.Color?>
<?import javafx.scene.shape.Rectangle?>
<VBox xmlns:fx="http://javafx.com/fxml">
<Rectangle width="100" height="100">
<fill>
<<warning descr="Conversion between unrelated types, javafx.geometry.Insets to javafx.scene.paint.Paint">Insets</warning> top="10"/>
</fill>
</Rectangle>
<spacing>
<<warning descr="Conversion between unrelated types, javafx.scene.paint.Color to double">Color</warning> green="1"/>
</spacing>
</VBox>
@@ -0,0 +1,10 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import java.lang.Double?>
<fx:root xmlns:fx="http://javafx.com/fxml" type="VBox">
<Rectangle width="100" height="100">
<fill>
<<warning descr="Conversion between unrelated types, java.lang.Double to javafx.scene.paint.Paint">Double</warning> fx:value="1"/>
</fill>
</Rectangle>
</fx:root>