javafx: Simplify resources for inspection of redundant FXML attributes and tags, fixed resource reloading after GC, move reused code to utility class (IDEA-102276)

This commit is contained in:
Pavel Dolgov
2016-03-03 16:15:59 +03:00
parent 94415b0079
commit 9d0faa9992
5 changed files with 877 additions and 973 deletions
@@ -2,9 +2,14 @@ package org.jetbrains.plugins.javaFX.fxml;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Pos;
import javafx.scene.AccessibleRole;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.PopupWindow;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;
@@ -20,7 +25,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* This is not a test, this is a config generator for JavaFxRedundantPropertyValueInspection
* This is not a test, this is a resource generator for JavaFxRedundantPropertyValueInspection
* <p>
* When launched without arguments it produces default values for JavaFX classes having default constructor and their superclasses, including some (but not all) abstract classes
* <p>
@@ -59,70 +64,57 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
});
}
private static final Map<Class, Class> ourToBoxed = new HashMap<>();
private static final Map<String, Object> ourFromSource = new TreeMap<>();
static {
ourToBoxed.put(Boolean.TYPE, Boolean.class);
ourToBoxed.put(Character.TYPE, Character.class);
ourToBoxed.put(Byte.TYPE, Byte.class);
ourToBoxed.put(Short.TYPE, Short.class);
ourToBoxed.put(Integer.TYPE, Integer.class);
ourToBoxed.put(Long.TYPE, Long.class);
ourToBoxed.put(Float.TYPE, Float.class);
ourToBoxed.put(Double.TYPE, Double.class);
}
private static final Map<String, String> ourFromSource = new TreeMap<>();
static {
ourFromSource.put("javafx.concurrent.ScheduledService#maximumFailureCount", "Integer=2147483647");
ourFromSource.put("javafx.concurrent.ScheduledService#restartOnFailure", "Boolean=true");
ourFromSource.put("javafx.scene.Node#accessibleRole", "Enum=NODE");
ourFromSource.put("javafx.scene.Node#focusTraversable", "Boolean=false");
ourFromSource.put("javafx.scene.Node#nodeOrientation", "Enum=INHERIT");
ourFromSource.put("javafx.scene.Node#pickOnBounds", "Boolean=false");
ourFromSource.put("javafx.scene.SubScene#height", "Double=0.0");
ourFromSource.put("javafx.scene.SubScene#width", "Double=0.0");
ourFromSource.put("javafx.scene.chart.AreaChart#createSymbols", "Boolean=true");
ourFromSource.put("javafx.scene.chart.Axis#label", "String=");
ourFromSource.put("javafx.scene.chart.BarChart#barGap", "Double=4.0");
ourFromSource.put("javafx.scene.chart.BarChart#categoryGap", "Double=10.0");
ourFromSource.put("javafx.scene.chart.Chart#title", "String=");
ourFromSource.put("javafx.scene.chart.LineChart#axisSortingPolicy", "Enum=X_AXIS");
ourFromSource.put("javafx.scene.chart.LineChart#createSymbols", "Boolean=true");
ourFromSource.put("javafx.scene.chart.StackedAreaChart#createSymbols", "Boolean=true");
ourFromSource.put("javafx.scene.chart.StackedBarChart#categoryGap", "Double=10.0");
ourFromSource.put("javafx.scene.chart.XYChart#alternativeColumnFillVisible", "Boolean=false");
ourFromSource.put("javafx.scene.chart.XYChart#alternativeRowFillVisible", "Boolean=true");
ourFromSource.put("javafx.scene.chart.XYChart#horizontalGridLinesVisible", "Boolean=true");
ourFromSource.put("javafx.scene.chart.XYChart#horizontalZeroLineVisible", "Boolean=true");
ourFromSource.put("javafx.scene.chart.XYChart#verticalGridLinesVisible", "Boolean=true");
ourFromSource.put("javafx.scene.chart.XYChart#verticalZeroLineVisible", "Boolean=true");
ourFromSource.put("javafx.scene.control.ComboBoxBase#editable", "Boolean=false");
ourFromSource.put("javafx.scene.control.CustomMenuItem#hideOnClick", "Boolean=true");
ourFromSource.put("javafx.scene.control.Labeled#alignment", "Enum=CENTER_LEFT");
ourFromSource.put("javafx.scene.control.Labeled#mnemonicParsing", "Boolean=false");
ourFromSource.put("javafx.scene.control.SpinnerValueFactory#wrapAround", "Boolean=false");
ourFromSource.put("javafx.scene.control.TableSelectionModel#cellSelectionEnabled", "Boolean=false");
ourFromSource.put("javafx.scene.media.AudioClip#balance", "Double=0.0");
ourFromSource.put("javafx.scene.media.AudioClip#cycleCount", "Integer=1");
ourFromSource.put("javafx.scene.media.AudioClip#pan", "Double=0.0");
ourFromSource.put("javafx.scene.media.AudioClip#priority", "Integer=0");
ourFromSource.put("javafx.scene.media.AudioClip#rate", "Double=1.0");
ourFromSource.put("javafx.scene.media.AudioClip#volume", "Double=1.0");
ourFromSource.put("javafx.scene.media.AudioEqualizer#enabled", "Boolean=false");
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumInterval", "Double=0.1");
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumNumBands", "Integer=128");
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumThreshold", "Integer=-60");
ourFromSource.put("javafx.scene.media.MediaPlayer#autoPlay", "Boolean=false");
ourFromSource.put("javafx.scene.media.MediaPlayer#balance", "Double=0.0");
ourFromSource.put("javafx.scene.media.MediaPlayer#cycleCount", "Integer=1");
ourFromSource.put("javafx.scene.media.MediaPlayer#mute", "Boolean=false");
ourFromSource.put("javafx.scene.media.MediaPlayer#rate", "Double=1.0");
ourFromSource.put("javafx.scene.media.MediaPlayer#volume", "Double=1.0");
ourFromSource.put("javafx.stage.PopupWindow#anchorLocation", "Enum=WINDOW_TOP_LEFT");
ourFromSource.put("javafx.stage.PopupWindow#autoHide", "Boolean=false");
ourFromSource.put("javafx.stage.PopupWindow#consumeAutoHidingEvents", "Boolean=true");
ourFromSource.put("javafx.concurrent.ScheduledService#maximumFailureCount", Integer.MAX_VALUE);
ourFromSource.put("javafx.concurrent.ScheduledService#restartOnFailure", true);
ourFromSource.put("javafx.scene.Node#accessibleRole", AccessibleRole.NODE);
ourFromSource.put("javafx.scene.Node#focusTraversable", false);
ourFromSource.put("javafx.scene.Node#nodeOrientation", NodeOrientation.INHERIT);
ourFromSource.put("javafx.scene.Node#pickOnBounds", false);
ourFromSource.put("javafx.scene.SubScene#height", 0.0);
ourFromSource.put("javafx.scene.SubScene#width", 0.0);
ourFromSource.put("javafx.scene.chart.AreaChart#createSymbols", true);
ourFromSource.put("javafx.scene.chart.Axis#label", "");
ourFromSource.put("javafx.scene.chart.BarChart#barGap", 4.0);
ourFromSource.put("javafx.scene.chart.BarChart#categoryGap", 10.0);
ourFromSource.put("javafx.scene.chart.Chart#title", "");
ourFromSource.put("javafx.scene.chart.LineChart#axisSortingPolicy", LineChart.SortingPolicy.X_AXIS);
ourFromSource.put("javafx.scene.chart.LineChart#createSymbols", true);
ourFromSource.put("javafx.scene.chart.StackedAreaChart#createSymbols", true);
ourFromSource.put("javafx.scene.chart.StackedBarChart#categoryGap", 10.0);
ourFromSource.put("javafx.scene.chart.XYChart#alternativeColumnFillVisible", false);
ourFromSource.put("javafx.scene.chart.XYChart#alternativeRowFillVisible", true);
ourFromSource.put("javafx.scene.chart.XYChart#horizontalGridLinesVisible", true);
ourFromSource.put("javafx.scene.chart.XYChart#horizontalZeroLineVisible", true);
ourFromSource.put("javafx.scene.chart.XYChart#verticalGridLinesVisible", true);
ourFromSource.put("javafx.scene.chart.XYChart#verticalZeroLineVisible", true);
ourFromSource.put("javafx.scene.control.ComboBoxBase#editable", false);
ourFromSource.put("javafx.scene.control.CustomMenuItem#hideOnClick", true);
ourFromSource.put("javafx.scene.control.Labeled#alignment", Pos.CENTER_LEFT);
ourFromSource.put("javafx.scene.control.Labeled#mnemonicParsing", false);
ourFromSource.put("javafx.scene.control.SpinnerValueFactory#wrapAround", false);
ourFromSource.put("javafx.scene.control.TableSelectionModel#cellSelectionEnabled", false);
ourFromSource.put("javafx.scene.media.AudioClip#balance", 0.0);
ourFromSource.put("javafx.scene.media.AudioClip#cycleCount", 1);
ourFromSource.put("javafx.scene.media.AudioClip#pan", 0.0);
ourFromSource.put("javafx.scene.media.AudioClip#priority", 0);
ourFromSource.put("javafx.scene.media.AudioClip#rate", 1.0);
ourFromSource.put("javafx.scene.media.AudioClip#volume", 1.0);
ourFromSource.put("javafx.scene.media.AudioEqualizer#enabled", false);
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumInterval", 0.1);
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumNumBands", 128);
ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumThreshold", -60);
ourFromSource.put("javafx.scene.media.MediaPlayer#autoPlay", false);
ourFromSource.put("javafx.scene.media.MediaPlayer#balance", 0.0);
ourFromSource.put("javafx.scene.media.MediaPlayer#cycleCount", 1);
ourFromSource.put("javafx.scene.media.MediaPlayer#mute", false);
ourFromSource.put("javafx.scene.media.MediaPlayer#rate", 1.0);
ourFromSource.put("javafx.scene.media.MediaPlayer#volume", 1.0);
ourFromSource.put("javafx.stage.PopupWindow#anchorLocation", PopupWindow.AnchorLocation.WINDOW_TOP_LEFT);
ourFromSource.put("javafx.stage.PopupWindow#autoHide", false);
ourFromSource.put("javafx.stage.PopupWindow#consumeAutoHidingEvents", true);
}
private static final Set<String> ourSkippedProperties = new HashSet<>(
@@ -130,56 +122,6 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
"javafx.scene.web.WebEngine#userAgent",
"javafx.scene.control.ButtonBar#buttonOrder"));
static class TypedValue {
private final String kind;
private final String value;
TypedValue(@NotNull String kind, @NotNull String value) {
this.kind = kind;
this.value = value;
}
public String getKind() {
return kind;
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TypedValue)) return false;
TypedValue that = (TypedValue)o;
return kind.equals(that.kind) && value.equals(that.value);
}
@Override
public int hashCode() {
return kind.hashCode() ^ value.hashCode();
}
@Override
public String toString() {
return kind + ':' + value;
}
}
static class ContainedValue extends TypedValue {
private final String declaringClass;
public ContainedValue(@NotNull String kind, @NotNull String value, @NotNull String declaringClass) {
super(kind, value);
this.declaringClass = declaringClass;
}
public String getDeclaringClass() {
return declaringClass;
}
}
/**
* Attempt to instantiate JavaFX classes on the FX thread, obtain default property values from instantiated objects.
@@ -187,8 +129,8 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
private static void generate() {
System.out.println("--- JavaFX default property values ---");
final Map<String, Map<String, ContainedValue>> containedProperties = new TreeMap<>();
final Map<String, Map<String, TypedValue>> declaredProperties = new TreeMap<>();
final Map<String, Map<String, DefaultValue>> defaultPropertyValues = new TreeMap<>();
final Map<String, Map<String, String>> declaredProperties = new TreeMap<>();
final Map<String, Map<String, Set<String>>> overriddenProperties = new TreeMap<>();
final Map<String, String> superClasses = new TreeMap<>();
try (final ZipInputStream zip = new ZipInputStream(new FileInputStream(new File(BINARIES_PATH)))) {
@@ -213,7 +155,7 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
}
}
Object obj = null;
Object instance = null;
for (PropertyDescriptor desc : info.getPropertyDescriptors()) {
final String propName = desc.getName();
final String propQualifiedName = currentClass.getName() + "#" + propName;
@@ -223,48 +165,41 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
if (setter == null || !hasBinding) continue;
final Type type = setter.getGenericParameterTypes()[0];
if (type instanceof Class) {
final Class<?> paramCls = (Class)type;
final String kind = kind(paramCls);
if (kind != null) {
if (obj == null) {
obj = instantiate(currentClass);
if (obj == null) break;
if (type instanceof Class && isSupportedPropertyType((Class)type)) {
if (instance == null) {
instance = instantiate(currentClass);
if (instance == null) break;
}
final Object value;
final Method getter = desc.getReadMethod();
try {
value = getter.invoke(instance);
}
catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Can't invoke " + getter + " on " + currentClass, e);
}
if (value != null) {
final Class<?> declaringClass = getter.getDeclaringClass();
final DefaultValue newValue = new DefaultValue(value, declaringClass.getName());
if (declaringClass.getName().startsWith("javafx")) {
defaultPropertyValues
.computeIfAbsent(currentClass.getName(), unused -> new TreeMap<>())
.put(propName, newValue);
}
final Object value;
final Method getter = desc.getReadMethod();
try {
value = getter.invoke(obj);
}
catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Can't invoke " + getter + " on " + currentClass, e);
}
if (value != null) {
if (!value.equals(validate(paramCls, value))) {
throw new RuntimeException("Invalid " + currentClass + "#" + propName + ":" + paramCls + "=" + value);
}
final Class<?> declaringClass = getter.getDeclaringClass();
final ContainedValue newValue = new ContainedValue(kind, String.valueOf(value), declaringClass.getName());
if (declaringClass.getName().startsWith("javafx")) {
containedProperties
.computeIfAbsent(currentClass.getName(), unused -> new TreeMap<>())
.put(propName, newValue);
}
final Map<String, TypedValue> shareableProperties =
declaredProperties.computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>());
final TypedValue sharedValue = shareableProperties.get(propName);
final Map<String, String> shareableProperties =
declaredProperties.computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>());
final String sharedValue = shareableProperties.get(propName);
if (sharedValue == null) {
shareableProperties.put(propName, newValue);
}
else if (!sharedValue.equals(newValue)) {
final Set<String> multipleValues = overriddenProperties
.computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>())
.computeIfAbsent(propName, unused -> new TreeSet<>());
multipleValues.add(sharedValue.getValue());
multipleValues.add(newValue.getValue());
}
if (sharedValue == null) {
shareableProperties.put(propName, newValue.getValueText());
}
else if (!sharedValue.equals(newValue.getValueText())) {
final Set<String> multipleValues = overriddenProperties
.computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>())
.computeIfAbsent(propName, unused -> new TreeSet<>());
multipleValues.add(sharedValue);
multipleValues.add(newValue.getValueText());
}
}
}
@@ -277,60 +212,51 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
}
System.out.println("-------- Collected from sources ---------");
ourFromSource.forEach((k, v) -> System.out.println(k + ":" + v));
ourFromSource.forEach((qualifiedPropName, value) -> System.out.println(qualifiedPropName + "=" + value));
System.out.println("-------- Shared (not overridden) ---------");
declaredProperties.forEach(
(className, propertyMap) -> {
final Map<String, Set<String>> multipleValues = overriddenProperties.getOrDefault(className, Collections.emptyMap());
propertyMap.forEach((propName, typedValue) -> {
propertyMap.forEach((propName, valueText) -> {
if (!multipleValues.containsKey(propName)) {
System.out.println(className + "#" + propName + ":" + typedValue.getKind() + "=" + typedValue.getValue());
System.out.println(className + "#" + propName + "=" + valueText);
}
});
});
System.out.println("-------- Overridden in subclass ---------");
final Map<String, Map<String, ContainedValue>> fromSource = new TreeMap<>();
ourFromSource.forEach((classAndPropName, kindAndValue) -> {
final int p1 = classAndPropName.indexOf('#');
if (p1 > 0 && p1 < classAndPropName.length()) {
final String className = classAndPropName.substring(0, p1);
final String propName = classAndPropName.substring(p1 + 1);
final int p3 = kindAndValue.indexOf('=');
if (p3 > 0 && p3 < kindAndValue.length()) {
final String kind = kindAndValue.substring(0, p3);
final String value = kindAndValue.substring(p3 + 1);
final Map<String, ContainedValue> propMap = fromSource.computeIfAbsent(className, unused -> new TreeMap<>());
if (!propMap.containsKey(propName)) {
final ContainedValue valueFromSource = new ContainedValue(kind, value, className);
propMap.put(propName, valueFromSource);
}
}
final Map<String, Map<String, DefaultValue>> fromSource = new TreeMap<>();
ourFromSource.forEach((qualifiedPropName, value) -> {
final int p = qualifiedPropName.indexOf('#');
if (p > 0 && p < qualifiedPropName.length()) {
final String className = qualifiedPropName.substring(0, p);
final String propName = qualifiedPropName.substring(p + 1);
fromSource.computeIfAbsent(className, unused -> new TreeMap<>())
.computeIfAbsent(propName, unused -> new DefaultValue(value, className));
}
});
containedProperties.forEach(
defaultPropertyValues.forEach(
(className, propertyMap) -> propertyMap.forEach(
(propName, propValue) -> {
final ContainedValue sourceValue = fromSource.getOrDefault(className, Collections.emptyMap()).get(propName);
if (sourceValue != null &&
sourceValue.equals(propValue)) {
final DefaultValue sourceValue = fromSource.getOrDefault(className, Collections.emptyMap()).get(propName);
if (sourceValue != null && areValuesEqual(propValue, sourceValue)) {
return;
}
final Map<String, Set<String>> multipleValues = overriddenProperties.get(propValue.getDeclaringClass());
if (multipleValues != null && multipleValues.get(propName) != null) {
boolean sameValueInSuperClass = false;
for (String scName = superClasses.get(className); scName != null; scName = superClasses.get(scName)) {
final Map<String, ContainedValue> superPropMap = containedProperties.getOrDefault(scName, fromSource.get(scName));
final Map<String, DefaultValue> superPropMap = defaultPropertyValues.getOrDefault(scName, fromSource.get(scName));
if (superPropMap != null) {
ContainedValue superValue = superPropMap.get(propName);
DefaultValue superValue = superPropMap.get(propName);
if (superValue != null) {
sameValueInSuperClass = superValue.equals(propValue);
sameValueInSuperClass = areValuesEqual(propValue, superValue);
break;
}
}
}
if (!sameValueInSuperClass) {
System.out.println(className + "#" + propName + ":" + propValue.getKind() + "=" + propValue.getValue());
System.out.println(className + "#" + propName + "=" + propValue.getValueText());
}
}
}));
@@ -343,46 +269,17 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
ourSkippedProperties.forEach(propName -> System.out.println("-- " + propName));
}
private static String kind(Class aCls) {
String kind = null;
if (aCls.isPrimitive()) {
kind = ourToBoxed.get(aCls).getSimpleName();
}
else if (aCls.isEnum()) {
kind = "Enum";
}
else if (Number.class.isAssignableFrom(aCls) || Boolean.class.isAssignableFrom(aCls) || Character.class.isAssignableFrom(aCls)) {
kind = aCls.getSimpleName();
}
else if (CharSequence.class.isAssignableFrom(aCls)) {
kind = "String";
}
return kind;
private static boolean areValuesEqual(DefaultValue first, DefaultValue second) {
return second.getValueText().equals(first.getValueText());
}
private static Object validate(Class<?> aCls, Object val) {
try {
if (aCls.isPrimitive()) {
aCls = ourToBoxed.get(aCls);
Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
return valueOf.invoke(null, String.valueOf(val));
}
if (Number.class.isAssignableFrom(aCls) || Boolean.class.isAssignableFrom(aCls) || Character.class.isAssignableFrom(aCls)) {
Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
return valueOf.invoke(null, String.valueOf(val));
}
if (CharSequence.class.isAssignableFrom(aCls)) {
return val;
}
if (aCls.isEnum()) {
Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
return valueOf.invoke(null, String.valueOf(val));
}
throw new IllegalStateException("Cannot cast " + val + " to unsupported class " + aCls);
}
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException("Cannot cast " + val + " to " + aCls, e);
}
private static boolean isSupportedPropertyType(Class<?> propertyClass) {
return propertyClass.isPrimitive() ||
propertyClass.isEnum() ||
Number.class.isAssignableFrom(propertyClass) ||
Boolean.class.isAssignableFrom(propertyClass) ||
Character.class.isAssignableFrom(propertyClass) ||
CharSequence.class.isAssignableFrom(propertyClass);
}
private static Object instantiate(Class<?> aClass) {
@@ -530,4 +427,27 @@ public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
System.out.println("--- Default values collected from JavaDoc and declarations. To be reviewed and manually edited ---");
props.forEach((n, v) -> System.out.println(n + "=" + v));
}
private static class DefaultValue {
private final String myValueText;
private final String myDeclaringClass;
public DefaultValue(@NotNull Object value, @NotNull String declaringClass) {
myValueText = String.valueOf(value);
myDeclaringClass = declaringClass;
}
public String getDeclaringClass() {
return myDeclaringClass;
}
public String getValueText() {
return myValueText;
}
@Override
public String toString() {
return myValueText;
}
}
}
@@ -446,7 +446,7 @@ public class JavaFxPsiUtil {
public static boolean hasBuilder(@NotNull final PsiClass psiClass) {
final Project project = psiClass.getProject();
return CachedValuesManager.getManager(project).getCachedValue(psiClass, new CachedValueProvider<Boolean>() {
return CachedValuesManager.getCachedValue(psiClass, new CachedValueProvider<Boolean>() {
@Nullable
@Override
public Result<Boolean> compute() {
@@ -604,8 +604,6 @@ public class JavaFxPsiUtil {
return null;
}
private static final Key<CachedValue<Map<String, XmlAttributeValue>>> FILE_IDS_KEY = Key.create("FILE_IDS_KEY");
@NotNull
public static Map<String, XmlAttributeValue> collectFileIds(@Nullable final XmlTag currentTag) {
if (currentTag == null) return Collections.emptyMap();
@@ -614,10 +612,8 @@ public class JavaFxPsiUtil {
final XmlTag rootTag = ((XmlFile)containingFile).getRootTag();
if (rootTag == null) return Collections.emptyMap();
final Map<String, XmlAttributeValue> cachedIds = CachedValuesManager.getManager(containingFile.getProject())
.getCachedValue(rootTag, FILE_IDS_KEY,
() -> new CachedValueProvider.Result<>(prepareFileIds(rootTag), PsiModificationTracker.MODIFICATION_COUNT), false);
final Map<String, XmlAttributeValue> cachedIds = CachedValuesManager
.getCachedValue(rootTag, () -> new CachedValueProvider.Result<>(prepareFileIds(rootTag), PsiModificationTracker.MODIFICATION_COUNT));
final XmlAttribute currentIdAttribute = currentTag.getAttribute(FxmlConstants.FX_ID);
if (currentIdAttribute != null) {
final String currentId = currentIdAttribute.getValue();
@@ -684,6 +680,21 @@ public class JavaFxPsiUtil {
|| findValueOfMethod(targetClass) != null;
}
@Nullable
public static String getBoxedPropertyType(PsiElement declaration) {
PsiType psiType = getWritablePropertyType(declaration);
if (psiType instanceof PsiPrimitiveType) {
return ((PsiPrimitiveType)psiType).getBoxedTypeName();
}
if (PsiPrimitiveType.getUnboxedType(psiType) != null) {
final PsiClass psiClass = PsiUtil.resolveClassInType(psiType);
if (psiClass != null) {
return psiClass.getQualifiedName();
}
}
return null;
}
private static class JavaFxControllerCachedValueProvider implements CachedValueProvider<PsiClass> {
private final Project myProject;
private final PsiFile myContainingFile;
@@ -1,12 +1,14 @@
package org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections;
import com.intellij.codeInsight.daemon.impl.analysis.RemoveAttributeIntentionFix;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
@@ -17,8 +19,9 @@ import gnu.trove.THashMap;
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.JavaFxFileTypeFactory;
import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxClassBackedElementDescriptor;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxPropertyAttributeDescriptor;
import org.jetbrains.plugins.javaFX.fxml.descriptors.JavaFxPropertyElementDescriptor;
@@ -26,8 +29,10 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.Reference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.LinkedHashSet;
import java.util.Collections;
import java.util.Map;
/**
@@ -36,7 +41,7 @@ import java.util.Map;
public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspectionTool {
private static final Logger LOG = Logger.getInstance("#" + JavaFxRedundantPropertyValueInspection.class.getName());
private static Reference<Map<String, Map<String, Object>>> ourDefaultPropertyValues;
private static Reference<Map<String, Map<String, String>>> ourDefaultPropertyValues;
@NotNull
@Override
@@ -64,7 +69,7 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
return;
}
final Object defaultValue = getDefaultValue(attributeName, attribute.getParent());
final Object defaultValue = getDefaultValue(descriptor, attributeName, attribute.getParent());
if (defaultValue == null) return;
if (isEqualValue(attributeValue, defaultValue)) {
@@ -78,8 +83,7 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
public void visitXmlTag(XmlTag tag) {
super.visitXmlTag(tag);
final XmlElementDescriptor descriptor = tag.getDescriptor();
if (!(descriptor instanceof JavaFxPropertyElementDescriptor) &&
!(descriptor instanceof JavaFxClassBackedElementDescriptor)) {
if (!(descriptor instanceof JavaFxPropertyElementDescriptor)) {
return;
}
if (tag.getSubTags().length != 0) return;
@@ -90,7 +94,7 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
return;
}
final Object defaultValue = getDefaultValue(tag.getName(), tag.getParentTag());
final Object defaultValue = getDefaultValue(descriptor, tag.getName(), tag.getParentTag());
if (defaultValue == null) return;
if (isEqualValue(tagText, defaultValue)) {
@@ -103,21 +107,19 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
}
@Nullable
private static Object getDefaultValue(@NotNull String propertyName, @Nullable XmlTag enclosingTag) {
private static Object getDefaultValue(PsiMetaData propertyDescriptor, @NotNull String propertyName, @Nullable XmlTag enclosingTag) {
if (enclosingTag != null) {
final XmlElementDescriptor descriptor = enclosingTag.getDescriptor();
if (descriptor != null) {
final PsiElement declaration = descriptor.getDeclaration();
if (declaration instanceof PsiClass) {
PsiClass containingClass = ((PsiClass)declaration);
{
final Object defaultValue = getDefaultPropertyValue(containingClass.getQualifiedName(), propertyName);
if (defaultValue != null) return defaultValue;
}
final LinkedHashSet<PsiClass> superClasses = InheritanceUtil.getSuperClasses(containingClass);
for (PsiClass superClass : superClasses) {
final Object defaultValue = getDefaultPropertyValue(superClass.getQualifiedName(), propertyName);
if (defaultValue != null) return defaultValue;
for (PsiClass psiClass = ((PsiClass)declaration); psiClass != null; psiClass = psiClass.getSuperClass()) {
final String qualifiedName = psiClass.getQualifiedName();
if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) break;
final String defaultValue = getDefaultPropertyValue(qualifiedName, propertyName);
if (defaultValue != null) {
return getBoxedValue(propertyDescriptor.getDeclaration(), defaultValue);
}
}
}
}
@@ -125,12 +127,25 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
return null;
}
private static Object getBoxedValue(PsiElement declaration, String value) {
String boxedQName = JavaFxPsiUtil.getBoxedPropertyType(declaration);
if (boxedQName == null) return value;
try {
final Class<?> boxedClass = Class.forName(boxedQName);
final Method method = boxedClass.getMethod(JavaFxCommonNames.VALUE_OF, String.class);
return method.invoke(boxedClass, value);
}
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException ignored) {
return value;
}
}
private static boolean isEqualValue(@NotNull String attributeValue, @NotNull Object defaultValue) {
if (defaultValue instanceof String && defaultValue.equals(attributeValue)) return true;
if (defaultValue instanceof Boolean) return defaultValue == Boolean.valueOf(attributeValue);
if (defaultValue instanceof Double) {
try {
return Double.compare((Double)defaultValue, Double.valueOf(attributeValue)) == 0;
return Double.compare((Double)defaultValue, Double.parseDouble(attributeValue)) == 0;
}
catch (NumberFormatException ignored) {
return false;
@@ -138,7 +153,7 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
}
if (defaultValue instanceof Integer) {
try {
return Integer.compare((Integer)defaultValue, Integer.valueOf(attributeValue)) == 0;
return Integer.compare((Integer)defaultValue, Integer.parseInt(attributeValue)) == 0;
}
catch (NumberFormatException ignored) {
return false;
@@ -147,33 +162,37 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
return false;
}
private static Object getDefaultPropertyValue(String classQualifiedName, String propertyName) {
final Map<String, Object> values = getDefaultPropertyValues(classQualifiedName);
@Nullable
private static String getDefaultPropertyValue(String classQualifiedName, String propertyName) {
final Map<String, String> values = getDefaultPropertyValues(classQualifiedName);
return values != null ? values.get(propertyName) : null;
}
/**
* Load property values config. The config is produced with the script JavaFxGenerateDefaultPropertyValuesScript (can be found in tests)
* Load property values resource. The resource is produced with the script JavaFxGenerateDefaultPropertyValuesScript (can be found in tests)
*/
private static Map<String, Object> getDefaultPropertyValues(String classQualifiedName) {
if (ourDefaultPropertyValues == null) {
ourDefaultPropertyValues = loadDefaultPropertyValues(JavaFxRedundantPropertyValueInspection.class.getSimpleName() + "8.txt");
@Nullable
private static Map<String, String> getDefaultPropertyValues(String classQualifiedName) {
Map<String, Map<String, String>> values = SoftReference.dereference(ourDefaultPropertyValues);
if (values == null) {
values = loadDefaultPropertyValues(JavaFxRedundantPropertyValueInspection.class.getSimpleName() + "8.txt");
ourDefaultPropertyValues = new SoftReference<>(values);
}
Map<String, Map<String, Object>> values = SoftReference.dereference(ourDefaultPropertyValues);
return values != null ? values.get(classQualifiedName) : null;
return values.get(classQualifiedName);
}
/**
* The file format is <code>ClassName#propertyName:type=value</code> per line, line with leading double dash (--) is commented out
*/
private static Reference<Map<String, Map<String, Object>>> loadDefaultPropertyValues(String resourceName) {
@NotNull
private static Map<String, Map<String, String>> loadDefaultPropertyValues(@NotNull String resourceName) {
final URL resource = JavaFxRedundantPropertyValueInspection.class.getResource(resourceName);
if (resource == null) {
LOG.warn("Resource not found: " + resourceName);
return null;
return Collections.emptyMap();
}
final Map<String, Map<String, Object>> result = new THashMap<>(200);
final Map<String, Map<String, String>> result = new THashMap<>(200);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), CharsetToolkit.UTF8_CHARSET))) {
for (String line : FileUtil.loadLines(reader)) {
if (line.isEmpty() || line.startsWith("--")) continue;
@@ -181,21 +200,14 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
final int p1 = line.indexOf('#');
if (p1 > 0 && p1 < line.length()) {
final String className = line.substring(0, p1);
final int p2 = line.indexOf(':', p1);
final int p2 = line.indexOf('=', p1);
if (p2 > p1 && p2 < line.length()) {
final String propertyName = line.substring(p1 + 1, p2);
final int p3 = line.indexOf('=', p2 + 1);
if (p3 > 0 && p3 < line.length()) {
final String type = line.substring(p2 + 1, p3);
final String text = line.substring(p3 + 1);
final Object value = parseValue(type, text);
if (value != null) {
lineParsed = true;
final Map<String, Object> properties = result.computeIfAbsent(className, ignored -> new THashMap<String, Object>());
if (properties.put(propertyName, value) != null) {
LOG.warn("Duplicate default property value " + line);
}
}
final String valueText = line.substring(p2 + 1);
lineParsed = true;
final Map<String, String> properties = result.computeIfAbsent(className, ignored -> new THashMap<>());
if (properties.put(propertyName, valueText) != null) {
LOG.warn("Duplicate default property value " + line);
}
}
}
@@ -205,35 +217,10 @@ public class JavaFxRedundantPropertyValueInspection extends XmlSuppressableInspe
}
}
catch (IOException e) {
LOG.warn("Cannot read resource: " + resourceName, e);
return null;
}
return new SoftReference<Map<String, Map<String, Object>>>(result);
}
@Nullable
private static Object parseValue(String type, String text) {
try {
switch (type) {
case "Boolean":
return Boolean.valueOf(text);
case "Integer":
return Integer.valueOf(text);
case "Double":
return Double.valueOf(text);
case "String":
case "Enum":
return text;
default:
LOG.warn("Unsupported value type " + type + " for '" + text + "'");
return null;
}
}
catch (NumberFormatException ignored) {
LOG.warn("Invalid format of " + type + ": '" + text + "'");
return null;
LOG.warn("Can't read resource: " + resourceName, e);
return Collections.emptyMap();
}
return result;
}
}
@@ -87,7 +87,7 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
return ArrayUtil.toStringArray(enumConstants);
}
final String propertyQName = getBoxedPropertyType(getDeclaration());
final String propertyQName = JavaFxPsiUtil.getBoxedPropertyType(getDeclaration());
if (CommonClassNames.JAVA_LANG_FLOAT.equals(propertyQName) || CommonClassNames.JAVA_LANG_DOUBLE.equals(propertyQName)) {
return new String[] {"Infinity", "-Infinity", "NaN", "-NaN"};
} else if (CommonClassNames.JAVA_LANG_BOOLEAN.equals(propertyQName)) {
@@ -193,7 +193,7 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
final PsiElement declaration = attributeDescriptor.getDeclaration();
final String boxedQName;
if (declaration != null) {
boxedQName = getBoxedPropertyType(declaration);
boxedQName = JavaFxPsiUtil.getBoxedPropertyType(declaration);
}
else {
final PsiClass tagClass = JavaFxPsiUtil.getTagClass((XmlAttributeValue)context);
@@ -236,20 +236,6 @@ public class JavaFxPropertyAttributeDescriptor extends BasicXmlAttributeDescript
return null;
}
@Nullable
private static String getBoxedPropertyType(PsiElement declaration) {
PsiType attrType = JavaFxPsiUtil.getWritablePropertyType(declaration);
String boxedQName = null;
if (attrType instanceof PsiPrimitiveType) {
boxedQName = ((PsiPrimitiveType)attrType).getBoxedTypeName();
} else if (PsiPrimitiveType.getUnboxedType(attrType) != null) {
final PsiClass attrClass = PsiUtil.resolveClassInType(attrType);
boxedQName = attrClass != null ? attrClass.getQualifiedName() : null;
}
return boxedQName;
}
@Override
public PsiElement getDeclaration() {
if (myPsiClass != null) {