Javafx: Refactor descriptors of built-in FXML attributes - extract attribute-specific validation and completion from the base class to specific classes, remove redundant code (IDEA-154239)

This commit is contained in:
Pavel Dolgov
2016-04-07 16:48:03 +03:00
parent 57b64b33f7
commit b3d8b7f24c
6 changed files with 177 additions and 126 deletions
@@ -62,6 +62,8 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
attributeValue.startsWith("#") ||
attributeValue.startsWith("%") ||
FxmlConstants.FX_ID.equals(attributeName) ||
FxmlConstants.FX_VALUE.equals(attributeName) ||
FxmlConstants.FX_CONSTANT.equals(attributeName) ||
FxmlConstants.FX_CONTROLLER.equals(attributeName)) {
return;
}
@@ -17,9 +17,10 @@ package org.jetbrains.plugins.javaFX.fxml.descriptors;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
@@ -35,27 +36,33 @@ public class JavaFxBuiltInAttributeDescriptor extends JavaFxPropertyAttributeDes
private final String myParentTagName;
public JavaFxBuiltInAttributeDescriptor(String name, PsiClass psiClass) {
private JavaFxBuiltInAttributeDescriptor(String name, PsiClass psiClass) {
super(name, psiClass);
myParentTagName = null;
}
public JavaFxBuiltInAttributeDescriptor(String name, String parentTagName) {
private JavaFxBuiltInAttributeDescriptor(String name, String parentTagName) {
super(name, null);
myParentTagName = parentTagName;
}
@Override
public boolean hasIdType() {
return getName().equals(FxmlConstants.FX_ID);
public static JavaFxBuiltInAttributeDescriptor create(String name, PsiClass psiClass) {
if (FxmlConstants.FX_ID.equals(name)) return new FxIdAttributeDescriptor(psiClass);
if (FxmlConstants.FX_VALUE.equals(name)) return new FxValueAttributeDescriptor(psiClass);
if (FxmlConstants.FX_CONSTANT.equals(name)) return new FxConstantAttributeDescriptor(psiClass);
return new JavaFxBuiltInAttributeDescriptor(name, psiClass);
}
public static JavaFxBuiltInAttributeDescriptor create(String name, String parentTagName) {
if (FxmlConstants.FX_ID.equals(name)) return new FxIdAttributeDescriptor(parentTagName);
if (FxmlConstants.FX_VALUE.equals(name)) return new FxValueAttributeDescriptor(parentTagName);
if (FxmlConstants.FX_CONSTANT.equals(name)) return new FxConstantAttributeDescriptor(parentTagName);
return new JavaFxBuiltInAttributeDescriptor(name, parentTagName);
}
@Override
public boolean isEnumerated() {
final PsiClass psiClass = getPsiClass();
if (psiClass == null) return false;
final String name = getName();
return FxmlConstants.FX_CONSTANT.equals(name) || psiClass.isEnum() && FxmlConstants.FX_VALUE.equals(name);
return false;
}
@Override
@@ -65,53 +72,121 @@ public class JavaFxBuiltInAttributeDescriptor extends JavaFxPropertyAttributeDes
return requiredAttrs != null && requiredAttrs.contains(getName());
}
@Override
protected PsiClass getEnum() {
return isEnumerated() ? getPsiClass() : null ;
}
protected boolean isConstant(PsiField field, boolean inEnum) {
if (inEnum) return field instanceof PsiEnumConstant;
return field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL) && field.hasModifierProperty(PsiModifier.PUBLIC);
}
@Nullable
@Override
public String validateValue(XmlElement context, String value) {
if (context instanceof XmlAttributeValue) {
final PsiElement parent = context.getParent();
if (parent instanceof XmlAttribute) {
final XmlAttribute attribute = (XmlAttribute)parent;
final String attributeName = attribute.getName();
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() + ".";
}
}
}
else if (FxmlConstants.FX_CONSTANT.equals(attributeName)) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass((XmlAttributeValue)context);
if (tagClass != null) {
final PsiField constField = tagClass.findFieldByName(value, true);
if (constField == null) {
return "Constant '" + value + "' is not found";
}
}
return null;
}
}
}
return super.validateValue(context, value);
}
@Override
public String toString() {
return myParentTagName != null ? myParentTagName + "#" + getName() : super.toString();
}
private static class FxIdAttributeDescriptor extends JavaFxBuiltInAttributeDescriptor {
private FxIdAttributeDescriptor(PsiClass psiClass) {
super(FxmlConstants.FX_ID, psiClass);
}
private FxIdAttributeDescriptor(String parentTagName) {
super(FxmlConstants.FX_ID, parentTagName);
}
@Override
public boolean hasIdType() {
return true;
}
@Nullable
@Override
protected String validateAttributeValue(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
final PsiClass controllerClass = JavaFxPsiUtil.getControllerClass(xmlAttributeValue.getContainingFile());
if (controllerClass != null) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass(xmlAttributeValue);
if (tagClass != null) {
final PsiField field = controllerClass.findFieldByName(value, true);
if (field != null && !InheritanceUtil.isInheritorOrSelf(tagClass, PsiUtil.resolveClassInType(field.getType()), true)) {
return "Cannot set " + tagClass.getQualifiedName() + " to field \'" + field.getName() + "\'";
}
}
}
return null;
}
}
private static class FxValueAttributeDescriptor extends JavaFxBuiltInAttributeDescriptor {
private FxValueAttributeDescriptor(PsiClass psiClass) {
super(FxmlConstants.FX_VALUE, psiClass);
}
private FxValueAttributeDescriptor(String parentTagName) {
super(FxmlConstants.FX_VALUE, parentTagName);
}
@Override
public boolean isEnumerated() {
final PsiClass psiClass = getPsiClass();
return psiClass != null && psiClass.isEnum();
}
@Override
protected PsiClass getEnum() {
final PsiClass psiClass = getPsiClass();
return psiClass.isEnum() ? psiClass : null;
}
protected boolean isConstant(PsiField field) {
return field instanceof PsiEnumConstant;
}
@Nullable
@Override
protected String validateAttributeValue(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass(xmlAttributeValue);
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() + ".";
}
}
return super.validateAttributeValue(xmlAttributeValue, value);
}
}
private static class FxConstantAttributeDescriptor extends JavaFxBuiltInAttributeDescriptor {
private FxConstantAttributeDescriptor(PsiClass psiClass) {
super(FxmlConstants.FX_CONSTANT, psiClass);
}
private FxConstantAttributeDescriptor(String parentTagName) {
super(FxmlConstants.FX_CONSTANT, parentTagName);
}
@Override
public boolean isEnumerated() {
return getPsiClass() != null;
}
@Override
protected PsiClass getEnum() {
return getPsiClass();
}
protected boolean isConstant(PsiField field) {
return field.hasModifierProperty(PsiModifier.STATIC) &&
field.hasModifierProperty(PsiModifier.FINAL) &&
field.hasModifierProperty(PsiModifier.PUBLIC);
}
@Nullable
@Override
protected String validateAttributeValue(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass(xmlAttributeValue);
if (tagClass != null) {
final PsiField constField = tagClass.findFieldByName(value, true);
if (constField == null || !isConstant(constField)) {
return "Constant '" + value + "' is not found";
}
}
return null;
}
}
}
@@ -80,7 +80,7 @@ public class JavaFxBuiltInTagDescriptor implements XmlElementDescriptor, Validat
final List<String> builtInAttributeNames = FxmlConstants.FX_BUILT_IN_TAG_SUPPORTED_ATTRIBUTES.get(getName());
if (builtInAttributeNames != null) {
for (String attrName : builtInAttributeNames) {
descriptors.add(new JavaFxBuiltInAttributeDescriptor(attrName, getName()));
descriptors.add(JavaFxBuiltInAttributeDescriptor.create(attrName, getName()));
}
}
JavaFxClassTagDescriptorBase.collectStaticAttributesDescriptors(context, descriptors);
@@ -112,7 +112,7 @@ public class JavaFxBuiltInTagDescriptor implements XmlElementDescriptor, Validat
final List<String> defaultAttributeList = FxmlConstants.FX_BUILT_IN_TAG_SUPPORTED_ATTRIBUTES.get(getName());
if (defaultAttributeList != null) {
if (defaultAttributeList.contains(attributeName)) {
return new JavaFxBuiltInAttributeDescriptor(attributeName, getName());
return JavaFxBuiltInAttributeDescriptor.create(attributeName, getName());
}
final PsiMethod propertySetter = JavaFxPsiUtil.findStaticPropertySetter(attributeName, context);
if (propertySetter != null) {
@@ -236,36 +236,29 @@ public class JavaFxBuiltInTagDescriptor implements XmlElementDescriptor, Validat
@Override
public void validate(@NotNull XmlTag context, @NotNull ValidationHost host) {
final String contextName = context.getName();
if (FxmlConstants.FX_ROOT.equals(contextName)) {
if (context.getParentTag() != null) {
host.addMessage(context.getNavigationElement(), "<fx:root> is valid only as the root node of an FXML document",
ValidationHost.ErrorType.ERROR);
}
} else {
final XmlTag referencedTag = getReferencedTag(context);
if (referencedTag != null) {
final XmlElementDescriptor descriptor = referencedTag.getDescriptor();
if (descriptor != null) {
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
final PsiClass psiClass = (PsiClass)declaration;
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()) {
final PsiParameter[] parameters = constructor.getParameterList().getParameters();
if (parameters.length == 1 && psiClass == PsiUtil.resolveClassInType(parameters[0].getType())) {
copyConstructorFound = true;
break;
}
}
if (!copyConstructorFound) {
host.addMessage(context.getNavigationElement(), "Copy constructor not found for \'" + psiClass.getName() + "\'",
ValidationHost.ErrorType.ERROR);
final XmlTag referencedTag = getReferencedTag(context);
if (referencedTag != null) {
final XmlElementDescriptor descriptor = referencedTag.getDescriptor();
if (descriptor != null) {
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
final PsiClass psiClass = (PsiClass)declaration;
JavaFxPsiUtil.isClassAcceptable(context.getParentTag(), psiClass, (errorMessage, errorType) ->
host.addMessage(context.getNavigationElement(), errorMessage, errorType));
final String contextName = context.getName();
if (FxmlConstants.FX_COPY.equals(contextName)) {
boolean copyConstructorFound = false;
for (PsiMethod constructor : psiClass.getConstructors()) {
final PsiParameter[] parameters = constructor.getParameterList().getParameters();
if (parameters.length == 1 && psiClass == PsiUtil.resolveClassInType(parameters[0].getType())) {
copyConstructorFound = true;
break;
}
}
if (!copyConstructorFound) {
host.addMessage(context.getNavigationElement(), "Copy constructor not found for \'" + psiClass.getName() + "\'",
ValidationHost.ErrorType.ERROR);
}
}
}
}
@@ -217,7 +217,7 @@ public abstract class JavaFxClassTagDescriptorBase implements XmlElementDescript
collectInstanceProperties(descriptors);
collectStaticAttributesDescriptors(context, descriptors);
for (String builtInAttributeName : FxmlConstants.FX_BUILT_IN_ATTRIBUTES) {
descriptors.add(new JavaFxBuiltInAttributeDescriptor(builtInAttributeName, psiClass));
descriptors.add(JavaFxBuiltInAttributeDescriptor.create(builtInAttributeName, psiClass));
}
return descriptors.isEmpty() ? XmlAttributeDescriptor.EMPTY : descriptors.toArray(XmlAttributeDescriptor.EMPTY);
}
@@ -244,7 +244,7 @@ public abstract class JavaFxClassTagDescriptorBase implements XmlElementDescript
final PsiClass psiClass = getPsiClass();
if (psiClass == null) return null;
if (FxmlConstants.FX_BUILT_IN_ATTRIBUTES.contains(attributeName)) {
return new JavaFxBuiltInAttributeDescriptor(attributeName, psiClass);
return JavaFxBuiltInAttributeDescriptor.create(attributeName, psiClass);
}
final PsiMethod propertySetter = JavaFxPsiUtil.findStaticPropertySetter(attributeName, context);
if (propertySetter != null) {
@@ -4,13 +4,11 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.xml.impl.BasicXmlAttributeDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.fxml.JavaFxCommonNames;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
@@ -77,7 +75,7 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
final PsiField[] fields = aClass.getAllFields();
final List<String> enumConstants = new ArrayList<String>();
for (PsiField enumField : fields) {
if (isConstant(enumField, aClass.isEnum())) {
if (isConstant(enumField)) {
enumConstants.add(enumField.getName());
}
}
@@ -94,8 +92,8 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
return null;
}
protected boolean isConstant(PsiField field, boolean inEnum) {
return inEnum && field instanceof PsiEnumConstant;
protected boolean isConstant(PsiField field) {
return field instanceof PsiEnumConstant;
}
protected PsiClass getEnum() {
@@ -117,27 +115,25 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
@Override
public String validateValue(XmlElement context, String value) {
if (context instanceof XmlAttributeValue && value != null) {
final XmlAttributeValue xmlAttributeValue = (XmlAttributeValue)context;
final PsiElement parent = xmlAttributeValue.getParent();
if (parent instanceof XmlAttribute) {
final XmlAttribute xmlAttribute = (XmlAttribute)parent;
if (JavaFxPsiUtil.isEventHandlerProperty(xmlAttribute)) {
return validateAttributeHandler(xmlAttributeValue, value);
}
if (FxmlConstants.FX_ID.equals(xmlAttribute.getName())) {
return validateFxId(xmlAttributeValue, value);
}
if (value.startsWith("$")) {
return validatePropertyExpression(xmlAttributeValue, value);
}
else {
return validateLiteral(xmlAttributeValue, value);
}
}
return validateAttributeValue((XmlAttributeValue)context, value);
}
return null;
}
@Nullable
protected String validateAttributeValue(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
final PsiElement parent = xmlAttributeValue.getParent();
if (parent instanceof XmlAttribute && JavaFxPsiUtil.isEventHandlerProperty((XmlAttribute)parent)) {
return validateAttributeHandler(xmlAttributeValue, value);
}
if (value.startsWith("$")) {
return validatePropertyExpression(xmlAttributeValue, value);
}
else {
return validateLiteral(xmlAttributeValue, value);
}
}
@Nullable
private static String validateAttributeHandler(@NotNull XmlElement context, @NotNull String value) {
if (value.startsWith("#")) {
@@ -153,21 +149,6 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
return null;
}
@Nullable
private static String validateFxId(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
final PsiClass controllerClass = JavaFxPsiUtil.getControllerClass(xmlAttributeValue.getContainingFile());
if (controllerClass != null) {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass(xmlAttributeValue);
if (tagClass != null) {
final PsiField field = controllerClass.findFieldByName(value, true);
if (field != null && !InheritanceUtil.isInheritorOrSelf(tagClass, PsiUtil.resolveClassInType(field.getType()), true)) {
return "Cannot set " + tagClass.getQualifiedName() + " to field \'" + field.getName() + "\'";
}
}
}
return null;
}
@Nullable
private static String validatePropertyExpression(@NotNull XmlAttributeValue xmlAttributeValue, @NotNull String value) {
if (JavaFxPsiUtil.isIncorrectExpressionBinding(value)) {
@@ -100,7 +100,7 @@ public class JavaFxRootTagDescriptor extends JavaFxClassTagDescriptorBase {
return null;
}
protected boolean isConstant(PsiField field, boolean inEnum) {
protected boolean isConstant(PsiField field) {
return false;
}