javafx: simple expression binding (IDEA-100322)

This commit is contained in:
Anna Kozlova
2013-02-05 20:24:01 +04:00
parent 383a02e623
commit 03a223ce29
10 changed files with 170 additions and 26 deletions
@@ -22,7 +22,7 @@ import org.jetbrains.annotations.NonNls;
* Date: 1/16/13
*/
public class JavaFxCommonClassNames {
@NonNls public static final String JAVAFX_BEANS_PROPERTY_PROPERTY = "javafx.beans.property.Property";
@NonNls public static final String JAVAFX_BEANS_PROPERTY = "javafx.beans.property.Property";
@NonNls public static final String JAVAFX_ANCHOR_PANE = "javafx.scene.layout.AnchorPane";
@NonNls public static final String JAVAFX_EVENT = "javafx.event.Event";
@NonNls public static final String JAVAFX_BEANS_DEFAULT_PROPERTY = "javafx.beans.DefaultProperty";
@@ -296,4 +296,27 @@ public class JavaFxPsiUtil {
return false;
}
public static boolean isExpressionBinding(String value) {
if (!value.startsWith("$")) return false;
value = value.substring(1);
return value.startsWith("{") && value.endsWith("}") && value.contains(".");
}
@Nullable
public static PsiType getPropertyType(final PsiType type, final Project project) {
final PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass != null) {
final PsiClass propertyClass = JavaPsiFacade.getInstance(project).findClass(JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY,
GlobalSearchScope.allScope(project));
if (propertyClass != null) {
final PsiSubstitutor substitutor =
TypeConversionUtil.getClassSubstitutor(propertyClass, psiClass, PsiSubstitutor.EMPTY);
if (substitutor != null) {
return substitutor.substitute(propertyClass.getTypeParameters()[0]);
}
}
}
return null;
}
}
@@ -195,7 +195,7 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor,
if (field.hasModifierProperty(PsiModifier.STATIC)) continue;
final PsiType fieldType = field.getType();
if (!JavaFxPsiUtil.isReadOnly(field.getName(), context) &&
InheritanceUtil.isInheritor(fieldType, JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY_PROPERTY) ||
InheritanceUtil.isInheritor(fieldType, JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY) ||
fieldType.equalsToText(CommonClassNames.JAVA_LANG_STRING) ||
includeListProperties && GenericsHighlightUtil.getCollectionItemType(field.getType(), myPsiClass.getResolveScope()) != null) {
children.add(factory.fun(field));
@@ -59,15 +59,17 @@ public class JavaFxAnnotator implements Annotator {
if (!JavaFxFileTypeFactory.isFxml(containingFile)) return;
if (element instanceof XmlAttributeValue) {
final PsiReference[] references = element.getReferences();
for (PsiReference reference : references) {
final PsiElement resolve = reference.resolve();
if (resolve instanceof PsiMember) {
if (!JavaFxPsiUtil.isVisibleInFxml((PsiMember)resolve)) {
final String symbolPresentation = "'" + SymbolPresentationUtil.getSymbolPresentableText(resolve) + "'";
final Annotation annotation = holder.createErrorAnnotation(element,
symbolPresentation + (resolve instanceof PsiClass ? " should be public" : " should be public or annotated with @FXML"));
if (!(resolve instanceof PsiClass)) {
annotation.registerUniversalFix(new AddAnnotationFix(JavaFxCommonClassNames.JAVAFX_FXML_ANNOTATION, (PsiMember)resolve, ArrayUtil.EMPTY_STRING_ARRAY), null, null);
if (!JavaFxPsiUtil.isExpressionBinding(((XmlAttributeValue)element).getValue())) {
for (PsiReference reference : references) {
final PsiElement resolve = reference.resolve();
if (resolve instanceof PsiMember) {
if (!JavaFxPsiUtil.isVisibleInFxml((PsiMember)resolve)) {
final String symbolPresentation = "'" + SymbolPresentationUtil.getSymbolPresentableText(resolve) + "'";
final Annotation annotation = holder.createErrorAnnotation(element,
symbolPresentation + (resolve instanceof PsiClass ? " should be public" : " should be public or annotated with @FXML"));
if (!(resolve instanceof PsiClass)) {
annotation.registerUniversalFix(new AddAnnotationFix(JavaFxCommonClassNames.JAVAFX_FXML_ANNOTATION, (PsiMember)resolve, ArrayUtil.EMPTY_STRING_ARRAY), null, null);
}
}
}
}
@@ -16,18 +16,24 @@
package org.jetbrains.plugins.javaFX.fxml.refs;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
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.ProcessingContext;
import com.intellij.xml.XmlAttributeDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -57,24 +63,105 @@ class JavaFxComponentIdReferenceProvider extends PsiReferenceProvider {
}
});
return new PsiReference[]{new PsiReferenceBase<XmlAttributeValue>(xmlAttributeValue) {
@Override
public TextRange getRangeInElement() {
final TextRange rangeInElement = super.getRangeInElement();
return startsWithDollar ? new TextRange(rangeInElement.getStartOffset() + 1, rangeInElement.getEndOffset()) : rangeInElement;
}
if (JavaFxPsiUtil.isExpressionBinding(value)) {
final String expressionText = referencesId.substring(1, referencesId.length() - 1);
final String newId = StringUtil.getPackageName(expressionText);
final String fieldRef = StringUtil.getShortName(expressionText);
@Nullable
@Override
public PsiElement resolve() {
return fileIds.get(referencesId);
final JavaFxIdReferenceBase idReferenceBase = new JavaFxIdReferenceBase(xmlAttributeValue, fileIds, newId);
final TextRange range = idReferenceBase.getRangeInElement();
final int startOffset = range.getStartOffset() + 2;
final int endOffset = startOffset + newId.length();
idReferenceBase.setRangeInElement(new TextRange(startOffset, endOffset));
if (fileIds.containsKey(newId)) {
final XmlAttributeValue attributeValue = fileIds.get(newId);
final PsiClass tagClass = JavaFxPsiUtil.getTagClass(attributeValue);
if (tagClass != null) {
final JavaFxExpressionReferenceBase referenceBase = new JavaFxExpressionReferenceBase(xmlAttributeValue, tagClass, fieldRef);
final TextRange textRange = referenceBase.getRangeInElement();
referenceBase.setRangeInElement(new TextRange(endOffset + 1, textRange.getEndOffset() - 1));
return new PsiReference[] {idReferenceBase, referenceBase};
}
}
return new PsiReference[] {idReferenceBase};
}
final JavaFxIdReferenceBase idReferenceBase = new JavaFxIdReferenceBase(xmlAttributeValue, fileIds, referencesId);
if (startsWithDollar) {
final TextRange rangeInElement = idReferenceBase.getRangeInElement();
idReferenceBase.setRangeInElement(new TextRange(rangeInElement.getStartOffset() + 1, rangeInElement.getEndOffset()));
}
return new PsiReference[]{idReferenceBase};
}
@NotNull
@Override
public Object[] getVariants() {
return ArrayUtil.toStringArray(fileIds.keySet());
private static class JavaFxIdReferenceBase extends PsiReferenceBase<XmlAttributeValue> {
private final Map<String, XmlAttributeValue> myFileIds;
private final String myReferencesId;
public JavaFxIdReferenceBase(XmlAttributeValue xmlAttributeValue,
Map<String, XmlAttributeValue> fileIds,
String referencesId) {
super(xmlAttributeValue);
myFileIds = fileIds;
myReferencesId = referencesId;
}
@Nullable
@Override
public PsiElement resolve() {
return myFileIds.get(myReferencesId);
}
@NotNull
@Override
public Object[] getVariants() {
return ArrayUtil.toStringArray(myFileIds.keySet());
}
}
private static class JavaFxExpressionReferenceBase extends PsiReferenceBase<XmlAttributeValue> {
private final PsiClass myTagClass;
private final String myFieldName;
public JavaFxExpressionReferenceBase(XmlAttributeValue xmlAttributeValue, PsiClass tagClass, String fieldName) {
super(xmlAttributeValue);
myTagClass = tagClass;
myFieldName = fieldName;
}
@Nullable
@Override
public PsiElement resolve() {
return myTagClass.findFieldByName(myFieldName, true);
}
@NotNull
@Override
public Object[] getVariants() {
final PsiElement parent = getElement().getParent();
if (parent instanceof XmlAttribute) {
final XmlAttributeDescriptor descriptor = ((XmlAttribute)parent).getDescriptor();
if (descriptor != null) {
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiField) {
return collectProperties((PsiField)declaration);
}
}
}
}};
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
private Object[] collectProperties(@NotNull PsiField psiField) {
final PsiType type = psiField.getType();
final PsiType propertyType = JavaFxPsiUtil.getPropertyType(type, psiField.getProject());
final List<PsiField> objs = new ArrayList<PsiField>();
for (PsiField field : myTagClass.getAllFields()) {
if (field.hasModifierProperty(PsiModifier.STATIC)) continue;
final PsiType fieldType = field.getType();
if (TypeConversionUtil.isAssignable(type, fieldType) || (propertyType != null && TypeConversionUtil.isAssignable(propertyType, fieldType))) {
objs.add(field);
}
}
return ArrayUtil.toObjectArray(objs);
}
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Label?>
<GridPane xmlns:fx="http://javafx.com/fxml">
<TextField fx:id="textField"/>
<Label text="${textField.<caret>}"/>
</GridPane>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Label?>
<GridPane xmlns:fx="http://javafx.com/fxml">
<TextField fx:id="textField"/>
<Label text="${textField.text}"/>
</GridPane>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Label?>
<GridPane xmlns:fx="http://javafx.com/fxml">
<TextField fx:id="textField"/>
<Label text="${textField.text}"/>
</GridPane>
@@ -184,6 +184,10 @@ public class JavaFXHighlightingTest extends DaemonAnalyzerTestCase {
doDoTest(false, false);
}
public void testExpressionBinding() throws Exception {
doTest();
}
@NotNull
@Override
protected String getTestDataPath() {
@@ -93,6 +93,10 @@ public class JavaFxCompletionTest extends CompletionTestCase {
doTest();
}
public void testExpressionBinding() throws Exception {
doTest("text");
}
public void testReadOnly() throws Exception {
configureByFile(getTestName(true) + ".fxml");
assertTrue(myItems.length > 0);