blob: d80ba3845d50ddf5b96b3ca9a75f903ca71742b1 [file] [log] [blame]
/*
* Copyright 2005 Sascha Weinreuter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 30.04.2006
* Time: 16:52:31
*/
package org.intellij.lang.xpath.validation.inspections;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.Language;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.util.Alarm;
import org.intellij.lang.xpath.XPathFileType;
import org.intellij.lang.xpath.context.ContextProvider;
import org.intellij.lang.xpath.psi.XPathExpression;
import org.intellij.lang.xpath.psi.XPathFunctionCall;
import org.intellij.lang.xpath.psi.XPathType;
import org.intellij.lang.xpath.validation.ExpectedTypeUtil;
import org.intellij.lang.xpath.validation.inspections.quickfix.XPathQuickFixFactory;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.BitSet;
// TODO: Option to flag literals: <number> = '123', <string> = 123, etc.
public class ImplicitTypeConversion extends XPathInspection {
@NonNls
private static final String SHORT_NAME = "ImplicitTypeConversion";
public long BITS = 1720;
private final BitSet OPTIONS = new BitSet(12);
public boolean FLAG_EXPLICIT_CONVERSION = true;
public boolean IGNORE_NODESET_TO_BOOLEAN_VIA_STRING = true;
public ImplicitTypeConversion() {
update();
}
private void update() {
for (int i=0; i<12; i++) {
final boolean b = (BITS & (1 << i)) != 0;
OPTIONS.set(i, b);
}
}
@NotNull
public String getDisplayName() {
return "Implicit Type Conversion";
}
@NotNull
@NonNls
public String getShortName() {
return SHORT_NAME;
}
public boolean isEnabledByDefault() {
return true;
}
protected Visitor createVisitor(InspectionManager manager, boolean isOnTheFly) {
return new MyElementVisitor(manager, isOnTheFly);
}
@Nullable
public JComponent createOptionsPanel() {
return new Options();
}
public void readSettings(@NotNull Element node) throws InvalidDataException {
super.readSettings(node);
update();
}
public void writeSettings(@NotNull Element node) throws WriteExternalException {
BITS = 0;
for (int i=11; i>=0; i--) {
BITS <<= 1;
if (OPTIONS.get(i)) BITS |= 1;
}
super.writeSettings(node);
}
protected boolean acceptsLanguage(Language language) {
return language == XPathFileType.XPATH.getLanguage();
}
final class MyElementVisitor extends Visitor {
MyElementVisitor(InspectionManager manager, boolean isOnTheFly) {
super(manager, isOnTheFly);
}
protected void checkExpression(@NotNull XPathExpression expression) {
final XPathType expectedType = ExpectedTypeUtil.getExpectedType(expression);
// conversion to NODESET is impossible (at least not in a portable way) and is flagged by annotator
if (expectedType != XPathType.NODESET && expectedType != XPathType.UNKNOWN) {
final boolean isExplicit = FLAG_EXPLICIT_CONVERSION &&
ExpectedTypeUtil.isExplicitConversion(expression);
checkExpressionOfType(expression, expectedType, isExplicit);
}
}
private void checkExpressionOfType(@NotNull XPathExpression expression, XPathType type, boolean explicit) {
final XPathType exprType = expression.getType();
if (exprType.isAbstract() || type.isAbstract()) return;
if (exprType != type && (explicit || isCheckedConversion(exprType, type))) {
if (explicit && exprType == XPathType.STRING && type == XPathType.BOOLEAN) {
final XPathExpression expr = ExpectedTypeUtil.unparenthesize(expression);
if (expr instanceof XPathFunctionCall && IGNORE_NODESET_TO_BOOLEAN_VIA_STRING &&
((XPathFunctionCall)expr).getArgumentList()[0].getType() == XPathType.NODESET)
{
return;
}
}
final LocalQuickFix[] fixes;
if (type != XPathType.NODESET) {
final XPathQuickFixFactory fixFactory = ContextProvider.getContextProvider(expression).getQuickFixFactory();
explicit = explicit && !(exprType == XPathType.STRING && type == XPathType.BOOLEAN);
fixes = fixFactory.createImplicitTypeConversionFixes(expression, type, explicit);
} else {
fixes = null;
}
addProblem(myManager.createProblemDescriptor(expression,
"Expression should be of type '" + type.getName() + "'", myOnTheFly, fixes,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
}
}
private boolean isCheckedConversion(XPathType exprType, XPathType type) {
if (exprType == XPathType.NODESET) {
if (type == XPathType.STRING && OPTIONS.get(0)) return true;
if (type == XPathType.NUMBER && OPTIONS.get(4)) return true;
if (type == XPathType.BOOLEAN && OPTIONS.get(8)) return true;
} else if (exprType == XPathType.STRING) {
if (type == XPathType.NUMBER && OPTIONS.get(5)) return true;
if (type == XPathType.BOOLEAN && OPTIONS.get(9)) return true;
} else if (exprType == XPathType.NUMBER) {
if (type == XPathType.STRING && OPTIONS.get(2)) return true;
if (type == XPathType.BOOLEAN && OPTIONS.get(10)) return true;
} else if (exprType == XPathType.BOOLEAN) {
if (type == XPathType.STRING && OPTIONS.get(3)) return true;
if (type == XPathType.NUMBER && OPTIONS.get(11)) return true;
}
return false;
}
}
public class Options extends JPanel {
@SuppressWarnings({ "UNUSED_SYMBOL", "FieldCanBeLocal" })
private JPanel root;
private JCheckBox NS_S;
private JCheckBox NS_N;
private JCheckBox NS_B;
private JCheckBox S_S;
private JCheckBox S_N;
private JCheckBox S_B;
private JCheckBox N_S;
private JCheckBox N_N;
private JCheckBox N_B;
private JCheckBox B_S;
private JCheckBox B_N;
private JCheckBox B_B;
private JCheckBox myAlwaysFlagExplicitConversion;
private JCheckBox myIgnoreNodesetToString;
private final JCheckBox[][] matrix = new JCheckBox[][]{
{NS_S, S_S, N_S, B_S},
{NS_N, S_N, N_N, B_N},
{NS_B, S_B, N_B, B_B},
};
public Options() {
for (int i = 0; i < matrix.length; i++) {
JCheckBox[] row = matrix[i];
for (int j = 0; j < row.length; j++) {
JCheckBox to = row[j];
final int index = row.length * i + j;
to.setSelected(OPTIONS.get(index));
to.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
OPTIONS.set(index, e.getStateChange() == ItemEvent.SELECTED);
}
});
if (j == i + 1) to.setEnabled(false);
}
}
myAlwaysFlagExplicitConversion.setSelected(FLAG_EXPLICIT_CONVERSION);
myAlwaysFlagExplicitConversion.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
FLAG_EXPLICIT_CONVERSION = e.getStateChange() == ItemEvent.SELECTED;
}
});
myIgnoreNodesetToString.setSelected(IGNORE_NODESET_TO_BOOLEAN_VIA_STRING);
myIgnoreNodesetToString.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
IGNORE_NODESET_TO_BOOLEAN_VIA_STRING = e.getStateChange() == ItemEvent.SELECTED;
}
});
}
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
new Alarm(Alarm.ThreadToUse.SHARED_THREAD).addRequest(new Runnable() {
public void run() {
for (int i = 0; i < matrix.length; i++) {
JCheckBox[] row = matrix[i];
for (int j = 0; j < row.length; j++) {
JCheckBox to = row[j];
to.setEnabled(enabled && j != i + 1);
}
}
}
}, 200);
}
private void createUIComponents() {
root = this;
}
}
}