forked from phoedos/pmd
Beanify controllers, new settings persistence logic
Add property edition to the XPath panel
This commit is contained in:
@ -25,7 +25,7 @@ import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
/* default */ class RuleBuilder {
|
||||
public class RuleBuilder {
|
||||
|
||||
private List<PropertyDescriptor<?>> definedProperties = new ArrayList<>();
|
||||
private String name;
|
||||
@ -44,7 +44,7 @@ import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
private boolean isUsesMultifile;
|
||||
private boolean isUsesTyperesolution;
|
||||
|
||||
/* default */ RuleBuilder(String name, String clazz, String language) {
|
||||
public RuleBuilder(String name, String clazz, String language) {
|
||||
this.name = name;
|
||||
language(language);
|
||||
className(clazz);
|
||||
|
@ -45,6 +45,16 @@
|
||||
<artifactId>richtextfx</artifactId>
|
||||
<version>0.8.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.controlsfx</groupId>
|
||||
<artifactId>controlsfx</artifactId>
|
||||
<version>8.40.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils-core</artifactId>
|
||||
<version>1.8.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.pmd</groupId>
|
||||
<artifactId>pmd-apex</artifactId>
|
||||
|
@ -11,6 +11,7 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.collections.ObservableList;
|
||||
@ -47,7 +48,7 @@ public class Designer extends Application {
|
||||
parseParameters(getParameters());
|
||||
|
||||
FXMLLoader loader
|
||||
= new FXMLLoader(getClass().getResource("fxml/designer.fxml"));
|
||||
= new FXMLLoader(DesignerUtil.getFxml("designer.fxml"));
|
||||
|
||||
DesignerRoot owner = new DesignerRoot(stage);
|
||||
MainDesignerController mainController = new MainDesignerController(owner);
|
||||
|
@ -0,0 +1,240 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner;
|
||||
|
||||
import static net.sourceforge.pmd.properties.MultiValuePropertyDescriptor.DEFAULT_DELIMITER;
|
||||
import static net.sourceforge.pmd.properties.MultiValuePropertyDescriptor.DEFAULT_NUMERIC_DELIMITER;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil.rewire;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import org.controlsfx.validation.Severity;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.reactfx.util.Try;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.properties.PropertyTypeId;
|
||||
import net.sourceforge.pmd.properties.ValueParser;
|
||||
import net.sourceforge.pmd.properties.ValueParserConstants;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* Property edition dialog. Use {@link #bindToDescriptor(PropertyDescriptorSpec, ObservableList)} )}
|
||||
* to use this dialog to edit a descriptor spec. Typically owned by a {@link PropertyTableView}.
|
||||
* The controller must be instantiated by hand.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @see PropertyDescriptorSpec
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class EditPropertyDialogController implements Initializable {
|
||||
|
||||
private final Var<PropertyTypeId> typeId = Var.newSimpleVar(PropertyTypeId.STRING);
|
||||
private final Var<Runnable> commitHandler = Var.newSimpleVar(null);
|
||||
private Var<PropertyDescriptorSpec> backingDescriptor = Var.newSimpleVar(null);
|
||||
private Var<ObservableList<PropertyDescriptorSpec>> backingDescriptorList = Var.newSimpleVar(null);
|
||||
|
||||
private ValidationSupport validationSupport = new ValidationSupport();
|
||||
@FXML
|
||||
private TextField nameField;
|
||||
@FXML
|
||||
private TextField descriptionField;
|
||||
@FXML
|
||||
private ChoiceBox<PropertyTypeId> typeChoiceBox;
|
||||
@FXML
|
||||
private TextField valueField;
|
||||
@FXML
|
||||
private Button commitButton;
|
||||
|
||||
|
||||
public EditPropertyDialogController() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public EditPropertyDialogController(Runnable commitHandler) {
|
||||
this.commitHandler.setValue(commitHandler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
commitButton.setOnAction(e -> {
|
||||
commitHandler.ifPresent(Runnable::run);
|
||||
getStage().close();
|
||||
this.free();
|
||||
});
|
||||
|
||||
commitButton.disableProperty().bind(validationSupport.invalidProperty());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
typeId.bind(typeChoiceBox.getSelectionModel().selectedItemProperty());
|
||||
typeChoiceBox.setConverter(DesignerUtil.stringConverter(PropertyTypeId::getStringId,
|
||||
PropertyTypeId::lookupMnemonic));
|
||||
typeChoiceBox.getItems().addAll(PropertyTypeId.typeIdsToConstants().values());
|
||||
FXCollections.sort(typeChoiceBox.getItems());
|
||||
});
|
||||
|
||||
Platform.runLater(this::registerBasicValidators);
|
||||
|
||||
typeIdProperty().values()
|
||||
.filter(Objects::nonNull)
|
||||
.subscribe(this::registerTypeDependentValidators);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private Stage getStage() {
|
||||
return (Stage) commitButton.getScene().getWindow();
|
||||
}
|
||||
|
||||
|
||||
/** Unbinds this dialog from its backing properties. */
|
||||
public void free() {
|
||||
backingDescriptor.ifPresent(PropertyDescriptorSpec::unbind);
|
||||
backingDescriptor.setValue(null);
|
||||
backingDescriptorList.setValue(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wires this dialog to the descriptor, so that the controls edit the descriptor.
|
||||
*
|
||||
* @param spec The descriptor
|
||||
*/
|
||||
public void bindToDescriptor(PropertyDescriptorSpec spec, ObservableList<PropertyDescriptorSpec> allDescriptors) {
|
||||
backingDescriptor.setValue(spec);
|
||||
backingDescriptorList.setValue(allDescriptors);
|
||||
rewire(spec.nameProperty(), this.nameProperty(), this::setName);
|
||||
rewire(spec.typeIdProperty(), this.typeIdProperty(), this::setTypeId);
|
||||
rewire(spec.valueProperty(), this.valueProperty(), this::setValue);
|
||||
rewire(spec.descriptionProperty(), this.descriptionProperty(), this::setDescription);
|
||||
}
|
||||
|
||||
|
||||
// Validators for attributes common to all properties
|
||||
private void registerBasicValidators() {
|
||||
Validator<String> noWhitespaceName
|
||||
= Validator.createRegexValidator("Name cannot contain whitespace", "\\S*+", Severity.ERROR);
|
||||
Validator<String> emptyName = Validator.createEmptyValidator("Name required");
|
||||
Validator<String> uniqueName = (c, val) -> {
|
||||
long sameNameDescriptors = backingDescriptorList.getOrElse(FXCollections.emptyObservableList())
|
||||
.stream()
|
||||
.map(PropertyDescriptorSpec::getName)
|
||||
.filter(getName()::equals)
|
||||
.count();
|
||||
|
||||
return new ValidationResult().addErrorIf(c, "The name must be unique", sameNameDescriptors > 1);
|
||||
};
|
||||
|
||||
validationSupport.registerValidator(nameField, Validator.combine(noWhitespaceName, emptyName, uniqueName));
|
||||
|
||||
Validator<String> noWhitespaceDescription
|
||||
= Validator.createRegexValidator("Message cannot be whitespace", "(\\s*+\\S.*)?", Severity.ERROR);
|
||||
Validator<String> emptyDescription = Validator.createEmptyValidator("Message required");
|
||||
validationSupport.registerValidator(descriptionField, Validator.combine(noWhitespaceDescription, emptyDescription));
|
||||
}
|
||||
|
||||
|
||||
private void registerTypeDependentValidators(PropertyTypeId typeId) {
|
||||
Validator<String> valueValidator = (c, val) ->
|
||||
ValidationResult.fromErrorIf(valueField, "The value couldn't be parsed",
|
||||
Try.tryGet(() -> getValueParser(typeId).valueOf(getValue())).isFailure());
|
||||
|
||||
|
||||
validationSupport.registerValidator(valueField, valueValidator);
|
||||
}
|
||||
|
||||
|
||||
private ValueParser<?> getValueParser(PropertyTypeId typeId) {
|
||||
ValueParser<?> parser = typeId.getValueParser();
|
||||
if (typeId.isPropertyMultivalue()) {
|
||||
char delimiter = typeId.isPropertyNumeric() ? DEFAULT_NUMERIC_DELIMITER : DEFAULT_DELIMITER;
|
||||
parser = ValueParserConstants.multi(parser, delimiter);
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return nameField.getText();
|
||||
}
|
||||
|
||||
|
||||
public void setName(String name) {
|
||||
nameField.setText(name);
|
||||
}
|
||||
|
||||
|
||||
public Property<String> nameProperty() {
|
||||
return nameField.textProperty();
|
||||
}
|
||||
|
||||
|
||||
public String getDescription() {
|
||||
return descriptionField.getText();
|
||||
}
|
||||
|
||||
|
||||
public void setDescription(String description) {
|
||||
descriptionField.setText(description);
|
||||
}
|
||||
|
||||
|
||||
public Property<String> descriptionProperty() {
|
||||
return descriptionField.textProperty();
|
||||
}
|
||||
|
||||
|
||||
public PropertyTypeId getTypeId() {
|
||||
return typeId.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setTypeId(PropertyTypeId typeId) {
|
||||
typeChoiceBox.getSelectionModel().select(typeId);
|
||||
}
|
||||
|
||||
|
||||
public Var<PropertyTypeId> typeIdProperty() {
|
||||
return typeId;
|
||||
}
|
||||
|
||||
|
||||
public String getValue() {
|
||||
return valueField.getText();
|
||||
}
|
||||
|
||||
|
||||
public void setValue(String value) {
|
||||
valueField.setText(value);
|
||||
}
|
||||
|
||||
|
||||
public Property<String> valueProperty() {
|
||||
return valueField.textProperty();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
|
@ -5,7 +5,6 @@
|
||||
package net.sourceforge.pmd.util.fxdesigner;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.WeakHashMap;
|
||||
@ -58,10 +57,11 @@ public class ExportXPathWizardController implements Initializable {
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
languageChoiceBox.getItems().addAll(Arrays.stream(DesignerUtil.getSupportedLanguageVersions())
|
||||
.map(LanguageVersion::getLanguage)
|
||||
.distinct()
|
||||
.collect(Collectors.toList()));
|
||||
languageChoiceBox.getItems().addAll(DesignerUtil.getSupportedLanguageVersions()
|
||||
.stream()
|
||||
.map(LanguageVersion::getLanguage)
|
||||
.distinct()
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
languageChoiceBox.setConverter(new StringConverter<Language>() {
|
||||
@Override
|
||||
@ -117,34 +117,34 @@ public class ExportXPathWizardController implements Initializable {
|
||||
// TODO very inefficient, can we do better?
|
||||
|
||||
final String template = "<rule name=\"%s\"\n"
|
||||
+ " language=\"%s\"\n"
|
||||
+ " message=\"%s\"\n"
|
||||
+ " class=\"net.sourceforge.pmd.lang.rule.XPathRule\"\n"
|
||||
+ " <!-- externalInfoUrl=\"%s\"--> >\n"
|
||||
+ " <description>\n"
|
||||
+ "%s\n"
|
||||
+ " </description>\n"
|
||||
+ " <priority>%d</priority>\n"
|
||||
+ " <properties>\n"
|
||||
+ " <property name=\"xpath\">\n"
|
||||
+ " <value>\n"
|
||||
+ "<![CDATA[\n"
|
||||
+ "%s\n"
|
||||
+ "]]>\n"
|
||||
+ " </value>\n"
|
||||
+ " </property>\n"
|
||||
+ " </properties>\n"
|
||||
+ " <!--<example><![CDATA[]]></example>-->\n"
|
||||
+ "</rule>";
|
||||
+ " language=\"%s\"\n"
|
||||
+ " message=\"%s\"\n"
|
||||
+ " class=\"net.sourceforge.pmd.lang.rule.XPathRule\"\n"
|
||||
+ " <!-- externalInfoUrl=\"%s\"--> >\n"
|
||||
+ " <description>\n"
|
||||
+ "%s\n"
|
||||
+ " </description>\n"
|
||||
+ " <priority>%d</priority>\n"
|
||||
+ " <properties>\n"
|
||||
+ " <property name=\"xpath\">\n"
|
||||
+ " <value>\n"
|
||||
+ "<![CDATA[\n"
|
||||
+ "%s\n"
|
||||
+ "]]>\n"
|
||||
+ " </value>\n"
|
||||
+ " </property>\n"
|
||||
+ " </properties>\n"
|
||||
+ " <!--<example><![CDATA[]]></example>-->\n"
|
||||
+ "</rule>";
|
||||
|
||||
return String.format(template,
|
||||
nameField.getText(),
|
||||
languageChoiceBox.getSelectionModel().getSelectedItem().getTerseName(),
|
||||
messageField.getText(),
|
||||
"TODO",
|
||||
descriptionField.getText(), // TODO format
|
||||
(int) prioritySlider.getValue(),
|
||||
xpathExpression.getValue()
|
||||
nameField.getText(),
|
||||
languageChoiceBox.getSelectionModel().getSelectedItem().getTerseName(),
|
||||
messageField.getText(),
|
||||
"TODO",
|
||||
descriptionField.getText(), // TODO format
|
||||
(int) prioritySlider.getValue(),
|
||||
xpathExpression.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,8 @@ import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import org.reactfx.EventStreams;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
|
||||
import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator;
|
||||
@ -16,6 +18,7 @@ import net.sourceforge.pmd.lang.java.ast.TypeNode;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.MetricEvaluator;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeItem;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
@ -58,7 +61,7 @@ public class NodeInfoPanelController implements Initializable {
|
||||
private MetricEvaluator metricEvaluator = new MetricEvaluator();
|
||||
|
||||
|
||||
NodeInfoPanelController(DesignerRoot root, MainDesignerController mainController) {
|
||||
public NodeInfoPanelController(DesignerRoot root, MainDesignerController mainController) {
|
||||
this.designerRoot = root;
|
||||
parent = mainController;
|
||||
}
|
||||
@ -66,11 +69,13 @@ public class NodeInfoPanelController implements Initializable {
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
scopeHierarchyTreeView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null && newVal.getValue() instanceof NameDeclaration) {
|
||||
parent.onNameDeclarationSelected((NameDeclaration) newVal.getValue());
|
||||
}
|
||||
});
|
||||
EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty())
|
||||
.filter(Objects::nonNull)
|
||||
.map(TreeItem::getValue)
|
||||
.filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o)
|
||||
.subscribe(parent::onNameDeclarationSelected);
|
||||
|
||||
scopeHierarchyTreeView.setCellFactory(view -> new ScopeHierarchyTreeCell(parent));
|
||||
}
|
||||
|
||||
|
||||
@ -93,7 +98,8 @@ public class NodeInfoPanelController implements Initializable {
|
||||
.filter(result -> !result.isNaN())
|
||||
.count());
|
||||
|
||||
|
||||
// TODO maybe a better way would be to build all the scope TreeItem hierarchy once
|
||||
// and only expand the ascendants of the node.
|
||||
TreeItem<Object> rootScope = ScopeHierarchyTreeItem.buildAscendantHierarchy(node);
|
||||
scopeHierarchyTreeView.setRoot(rootScope);
|
||||
}
|
||||
@ -134,7 +140,7 @@ public class NodeInfoPanelController implements Initializable {
|
||||
while (attributeAxisIterator.hasNext()) {
|
||||
Attribute attribute = attributeAxisIterator.next();
|
||||
result.add(attribute.getName() + " = "
|
||||
+ ((attribute.getValue() != null) ? attribute.getStringValue() : "null"));
|
||||
+ ((attribute.getValue() != null) ? attribute.getStringValue() : "null"));
|
||||
}
|
||||
|
||||
if (node instanceof TypeNode) {
|
||||
|
@ -5,32 +5,31 @@
|
||||
package net.sourceforge.pmd.util.fxdesigner;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.richtext.LineNumberFactory;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.ASTManager;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.ParseAbortedException;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlighters;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.CustomCodeArea;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlighter;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeItem;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.settings.AppSetting;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.settings.SettingsOwner;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
@ -56,7 +55,7 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
private TreeView<Node> astTreeView;
|
||||
@FXML
|
||||
private CustomCodeArea codeEditorArea;
|
||||
private BooleanProperty isSyntaxHighlightingEnabled = new SimpleBooleanProperty(true);
|
||||
|
||||
private ASTManager astManager;
|
||||
|
||||
|
||||
@ -69,51 +68,32 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
initializeSyntaxHighlighting();
|
||||
initializeASTTreeView();
|
||||
languageVersionProperty().values()
|
||||
.filterMap(Objects::nonNull, LanguageVersion::getLanguage)
|
||||
.distinct()
|
||||
.subscribe(this::updateSyntaxHighlighter);
|
||||
|
||||
EventStreams.valuesOf(astTreeView.getSelectionModel().selectedItemProperty())
|
||||
.filterMap(Objects::nonNull, TreeItem::getValue)
|
||||
.subscribe(parent::onNodeItemSelected);
|
||||
|
||||
codeEditorArea.setParagraphGraphicFactory(LineNumberFactory.get(codeEditorArea));
|
||||
}
|
||||
|
||||
|
||||
private void initializeSyntaxHighlighting() {
|
||||
|
||||
isSyntaxHighlightingEnabled.bind(codeEditorArea.syntaxHighlightingEnabledProperty());
|
||||
|
||||
isSyntaxHighlightingEnabled.addListener(((observable, wasEnabled, isEnabled) -> {
|
||||
if (!wasEnabled && isEnabled) {
|
||||
updateSyntaxHighlighter();
|
||||
} else if (!isEnabled) {
|
||||
codeEditorArea.disableSyntaxHighlighting();
|
||||
}
|
||||
}));
|
||||
|
||||
astManager.languageVersionProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null && !newVal.equals(oldVal)) {
|
||||
updateSyntaxHighlighter();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void initializeASTTreeView() {
|
||||
|
||||
astTreeView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null && newVal.getValue() != null) {
|
||||
parent.onNodeItemSelected(newVal.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes the AST.
|
||||
*/
|
||||
public void refreshAST() {
|
||||
String source = codeEditorArea.getText();
|
||||
Node previous = astManager.compilationUnitProperty().get();
|
||||
String source = getText();
|
||||
Node previous = getCompilationUnit();
|
||||
Node current;
|
||||
|
||||
if (StringUtils.isBlank(source)) {
|
||||
astTreeView.setRoot(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
current = astManager.updateCompilationUnit(source);
|
||||
} catch (ParseAbortedException e) {
|
||||
@ -122,11 +102,8 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
}
|
||||
if (previous != current) {
|
||||
parent.invalidateAst();
|
||||
setUpToDateCompilationUnit(current);
|
||||
}
|
||||
|
||||
setUpToDateCompilationUnit(current);
|
||||
codeEditorArea.clearPrimaryStyleLayer();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -142,10 +119,11 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
}
|
||||
|
||||
|
||||
private void updateSyntaxHighlighter() {
|
||||
SyntaxHighlighter computer = AvailableSyntaxHighlighters.getComputerForLanguage(astManager.getLanguageVersion().getLanguage());
|
||||
if (computer != null) {
|
||||
codeEditorArea.setSyntaxHighlightingEnabled(computer);
|
||||
private void updateSyntaxHighlighter(Language language) {
|
||||
Optional<SyntaxHighlighter> highlighter = AvailableSyntaxHighlighters.getHighlighterForLanguage(language);
|
||||
|
||||
if (highlighter.isPresent()) {
|
||||
codeEditorArea.setSyntaxHighlightingEnabled(highlighter.get());
|
||||
} else {
|
||||
codeEditorArea.disableSyntaxHighlighting();
|
||||
}
|
||||
@ -162,7 +140,7 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
}
|
||||
|
||||
|
||||
private void highlightNodes(Collection<Node> nodes, Set<String> cssClasses) {
|
||||
private void highlightNodes(Collection<? extends Node> nodes, Set<String> cssClasses) {
|
||||
for (Node node : nodes) {
|
||||
if (codeEditorArea.isInRange(node)) {
|
||||
codeEditorArea.styleCss(node, cssClasses);
|
||||
@ -176,7 +154,7 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
}
|
||||
|
||||
|
||||
public void highlightNodesSecondary(Collection<Node> nodes) {
|
||||
public void highlightNodesSecondary(Collection<? extends Node> nodes) {
|
||||
highlightNodes(nodes, Collections.singleton("secondary-highlight"));
|
||||
}
|
||||
|
||||
@ -202,44 +180,45 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
codeEditorArea.requestFollowCaret();
|
||||
}
|
||||
|
||||
|
||||
public boolean isSyntaxHighlightingEnabled() {
|
||||
return isSyntaxHighlightingEnabled.get();
|
||||
}
|
||||
|
||||
|
||||
public ReadOnlyBooleanProperty syntaxHighlightingEnabledProperty() {
|
||||
return isSyntaxHighlightingEnabled;
|
||||
}
|
||||
|
||||
|
||||
public ObservableValue<String> sourceCodeProperty() {
|
||||
return codeEditorArea.textProperty();
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public LanguageVersion getLanguageVersion() {
|
||||
return astManager.getLanguageVersion();
|
||||
}
|
||||
|
||||
|
||||
public ObjectProperty<LanguageVersion> languageVersionProperty() {
|
||||
public void setLanguageVersion(LanguageVersion version) {
|
||||
astManager.setLanguageVersion(version);
|
||||
}
|
||||
|
||||
|
||||
public Var<LanguageVersion> languageVersionProperty() {
|
||||
return astManager.languageVersionProperty();
|
||||
}
|
||||
|
||||
|
||||
public Node getCompilationUnit() {
|
||||
return astManager.updateCompilationUnit();
|
||||
return astManager.getCompilationUnit();
|
||||
}
|
||||
|
||||
|
||||
public ObjectProperty<Node> compilationUnitProperty() {
|
||||
public Val<Node> compilationUnitProperty() {
|
||||
return astManager.compilationUnitProperty();
|
||||
}
|
||||
|
||||
|
||||
public void replaceText(String source) {
|
||||
codeEditorArea.replaceText(source);
|
||||
@PersistentProperty
|
||||
public String getText() {
|
||||
return codeEditorArea.getText();
|
||||
}
|
||||
|
||||
|
||||
public void setText(String expression) {
|
||||
codeEditorArea.replaceText(expression);
|
||||
}
|
||||
|
||||
|
||||
public Val<String> textProperty() {
|
||||
return Val.wrap(codeEditorArea.textProperty());
|
||||
}
|
||||
|
||||
|
||||
@ -247,26 +226,4 @@ public class SourceEditorController implements Initializable, SettingsOwner {
|
||||
codeEditorArea.clearStyleLayers();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<AppSetting> getSettings() {
|
||||
List<AppSetting> settings = new ArrayList<>();
|
||||
settings.add(new AppSetting("langVersion", () -> getLanguageVersion().getTerseName(),
|
||||
this::restoreLanguageVersion));
|
||||
|
||||
settings.add(new AppSetting("code", () -> codeEditorArea.getText(),
|
||||
e -> codeEditorArea.replaceText(e)));
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
private void restoreLanguageVersion(String name) {
|
||||
LanguageVersion version = LanguageRegistry.findLanguageVersionByTerseName(name);
|
||||
if (version != null) {
|
||||
astManager.languageVersionProperty().setValue(version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -4,41 +4,54 @@
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.ObservableXPathRuleBuilder;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluator;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.CustomCodeArea;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XPathSyntaxHighlighter;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.settings.AppSetting;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.settings.SettingsOwner;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView;
|
||||
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* XPath panel controller.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @see ExportXPathWizardController
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
@ -48,19 +61,26 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
|
||||
private final XPathEvaluator xpathEvaluator = new XPathEvaluator();
|
||||
|
||||
private final ObservableXPathRuleBuilder ruleBuilder = new ObservableXPathRuleBuilder();
|
||||
|
||||
|
||||
@FXML
|
||||
private PropertyTableView propertyView;
|
||||
@FXML
|
||||
private CustomCodeArea xpathExpressionArea;
|
||||
@FXML
|
||||
private TitledPane violationsTitledPane;
|
||||
@FXML
|
||||
private ListView<Node> xpathResultListView;
|
||||
|
||||
// Actually a child of the main view toolbar, but this controller is responsible for it
|
||||
private ChoiceBox<String> xpathVersionChoiceBox;
|
||||
|
||||
|
||||
XPathPanelController(DesignerRoot owner, MainDesignerController mainController) {
|
||||
public XPathPanelController(DesignerRoot owner, MainDesignerController mainController) {
|
||||
this.designerRoot = owner;
|
||||
parent = mainController;
|
||||
|
||||
getRuleBuilder().setClazz(XPathRule.class);
|
||||
}
|
||||
|
||||
|
||||
@ -68,12 +88,24 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
xpathExpressionArea.setSyntaxHighlightingEnabled(new XPathSyntaxHighlighter());
|
||||
|
||||
xpathResultListView.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
parent.onNodeItemSelected(newVal);
|
||||
}
|
||||
});
|
||||
EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty())
|
||||
.filter(Objects::nonNull)
|
||||
.subscribe(parent::onNodeItemSelected);
|
||||
|
||||
Platform.runLater(this::bindToParent);
|
||||
}
|
||||
|
||||
|
||||
// Binds the underlying rule parameters to the parent UI, disconnecting it from the wizard if need be
|
||||
private void bindToParent() {
|
||||
DesignerUtil.rewire(getRuleBuilder().languageProperty(),
|
||||
Val.map(parent.languageVersionProperty(), LanguageVersion::getLanguage));
|
||||
|
||||
DesignerUtil.rewire(getRuleBuilder().xpathVersionProperty(), parent.xpathVersionProperty());
|
||||
DesignerUtil.rewire(getRuleBuilder().xpathExpressionProperty(), xpathExpressionProperty());
|
||||
|
||||
DesignerUtil.rewire(getRuleBuilder().rulePropertiesProperty(),
|
||||
propertyView.rulePropertiesProperty(), propertyView::setRuleProperties);
|
||||
}
|
||||
|
||||
|
||||
@ -85,20 +117,8 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
versionItems.add(XPathRuleQuery.XPATH_1_0_COMPATIBILITY);
|
||||
versionItems.add(XPathRuleQuery.XPATH_2_0);
|
||||
|
||||
xpathVersionChoiceBox.getSelectionModel().select(xpathEvaluator.xpathVersionProperty().get());
|
||||
|
||||
choiceBox.setConverter(new StringConverter<String>() {
|
||||
@Override
|
||||
public String toString(String object) {
|
||||
return "XPath " + object;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String fromString(String string) {
|
||||
return string.substring(6);
|
||||
}
|
||||
});
|
||||
xpathVersionChoiceBox.getSelectionModel().select(XPathRuleQuery.XPATH_2_0);
|
||||
choiceBox.setConverter(DesignerUtil.stringConverter(s -> "XPath " + s, s -> s.substring(6)));
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +131,7 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
public void evaluateXPath(Node compilationUnit, LanguageVersion version) {
|
||||
|
||||
try {
|
||||
String xpath = xpathExpressionArea.getText();
|
||||
String xpath = getXpathExpression();
|
||||
|
||||
if (StringUtils.isBlank(xpath)) {
|
||||
xpathResultListView.getItems().clear();
|
||||
@ -119,7 +139,11 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
}
|
||||
|
||||
ObservableList<Node> results
|
||||
= FXCollections.observableArrayList(xpathEvaluator.evaluateQuery(compilationUnit, version, xpath));
|
||||
= FXCollections.observableArrayList(xpathEvaluator.evaluateQuery(compilationUnit,
|
||||
version,
|
||||
getXpathVersion(),
|
||||
xpath,
|
||||
ruleBuilder.getRuleProperties()));
|
||||
xpathResultListView.setItems(results);
|
||||
violationsTitledPane.setText("Matched nodes\t(" + results.size() + ")");
|
||||
} catch (XPathEvaluationException e) {
|
||||
@ -138,32 +162,71 @@ public class XPathPanelController implements Initializable, SettingsOwner {
|
||||
}
|
||||
|
||||
|
||||
public void showExportXPathToRuleWizard() throws IOException {
|
||||
// doesn't work for some reason
|
||||
ExportXPathWizardController wizard
|
||||
= new ExportXPathWizardController(xpathExpressionProperty());
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/xpath-export-wizard.fxml"));
|
||||
loader.setController(wizard);
|
||||
|
||||
final Stage dialog = new Stage();
|
||||
dialog.initOwner(designerRoot.getMainStage());
|
||||
dialog.setOnCloseRequest(e -> wizard.shutdown());
|
||||
dialog.initModality(Modality.WINDOW_MODAL);
|
||||
|
||||
Parent root = loader.load();
|
||||
Scene scene = new Scene(root);
|
||||
//stage.setTitle("PMD Rule Designer (v " + PMD.VERSION + ')');
|
||||
dialog.setScene(scene);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
public void shutdown() {
|
||||
xpathExpressionArea.disableSyntaxHighlighting();
|
||||
}
|
||||
|
||||
|
||||
public StringProperty xpathVersionProperty() {
|
||||
return xpathEvaluator.xpathVersionProperty();
|
||||
@PersistentProperty
|
||||
public String getXpathExpression() {
|
||||
return xpathExpressionArea.getText();
|
||||
}
|
||||
|
||||
|
||||
public void setXpathExpression(String expression) {
|
||||
xpathExpressionArea.replaceText(expression);
|
||||
}
|
||||
|
||||
|
||||
public Val<String> xpathExpressionProperty() {
|
||||
return Val.wrap(xpathExpressionArea.textProperty());
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public String getXpathVersion() {
|
||||
return getRuleBuilder().getXpathVersion();
|
||||
}
|
||||
|
||||
|
||||
public void setXpathVersion(String xpathVersion) {
|
||||
getRuleBuilder().setXpathVersion(xpathVersion);
|
||||
}
|
||||
|
||||
|
||||
public Var<String> xpathVersionProperty() {
|
||||
return getRuleBuilder().xpathVersionProperty();
|
||||
}
|
||||
|
||||
|
||||
private ObservableXPathRuleBuilder getRuleBuilder() {
|
||||
return ruleBuilder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<AppSetting> getSettings() {
|
||||
List<AppSetting> settings = new ArrayList<>();
|
||||
settings.add(new AppSetting("xpathVersion", () -> xpathEvaluator.xpathVersionProperty().getValue(),
|
||||
v -> {
|
||||
if (!"".equals(v)) {
|
||||
xpathEvaluator.xpathVersionProperty().setValue(v);
|
||||
}
|
||||
}));
|
||||
settings.add(new AppSetting("xpathCode", () -> xpathExpressionArea.getText(), (v) -> xpathExpressionArea.replaceText(v)));
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
public ObservableValue<String> xpathExpressionProperty() {
|
||||
return xpathExpressionArea.textProperty();
|
||||
public List<SettingsOwner> getChildrenSettingsNodes() {
|
||||
return Collections.singletonList(getRuleBuilder());
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ package net.sourceforge.pmd.util.fxdesigner.model;
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
@ -16,9 +18,6 @@ import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
|
||||
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
|
||||
/**
|
||||
* Main class of the model. Manages a compilation unit.
|
||||
@ -41,15 +40,11 @@ public class ASTManager {
|
||||
/**
|
||||
* Latest computed compilation unit (only null before the first call to {@link #updateCompilationUnit(String)})
|
||||
*/
|
||||
private ObjectProperty<Node> compilationUnit = new SimpleObjectProperty<>();
|
||||
private Var<Node> compilationUnit = Var.newSimpleVar(null);
|
||||
/**
|
||||
* Selected language version.
|
||||
*/
|
||||
private ObjectProperty<LanguageVersion> languageVersion = new SimpleObjectProperty<>();
|
||||
|
||||
{
|
||||
languageVersion.setValue(LanguageRegistry.findLanguageVersionByTerseName("java 8"));
|
||||
}
|
||||
private Var<LanguageVersion> languageVersion = Var.newSimpleVar(LanguageRegistry.getDefaultLanguage().getDefaultVersion());
|
||||
|
||||
|
||||
public ASTManager(DesignerRoot owner) {
|
||||
@ -58,21 +53,26 @@ public class ASTManager {
|
||||
|
||||
|
||||
public LanguageVersion getLanguageVersion() {
|
||||
return languageVersion.get();
|
||||
return languageVersion.getValue();
|
||||
}
|
||||
|
||||
|
||||
public ObjectProperty<LanguageVersion> languageVersionProperty() {
|
||||
public void setLanguageVersion(LanguageVersion version) {
|
||||
languageVersion.setValue(version);
|
||||
}
|
||||
|
||||
|
||||
public Var<LanguageVersion> languageVersionProperty() {
|
||||
return languageVersion;
|
||||
}
|
||||
|
||||
|
||||
public Node updateCompilationUnit() {
|
||||
return compilationUnit.get();
|
||||
public Node getCompilationUnit() {
|
||||
return compilationUnit.getValue();
|
||||
}
|
||||
|
||||
|
||||
public ObjectProperty<Node> compilationUnitProperty() {
|
||||
public Val<Node> compilationUnitProperty() {
|
||||
return compilationUnit;
|
||||
}
|
||||
|
||||
@ -81,14 +81,16 @@ public class ASTManager {
|
||||
* Refreshes the compilation unit given the current parameters of the model.
|
||||
*
|
||||
* @param source Source code
|
||||
*
|
||||
* @throws ParseAbortedException if parsing fails and cannot recover
|
||||
*/
|
||||
public Node updateCompilationUnit(String source) throws ParseAbortedException {
|
||||
if (compilationUnit.get() != null
|
||||
&& languageVersion.get().equals(lastLanguageVersion) && StringUtils.equals(source, lastValidSource)) {
|
||||
return compilationUnit.get();
|
||||
if (compilationUnit.isPresent()
|
||||
&& getLanguageVersion().equals(lastLanguageVersion)
|
||||
&& StringUtils.equals(source, lastValidSource)) {
|
||||
return getCompilationUnit();
|
||||
}
|
||||
LanguageVersionHandler languageVersionHandler = languageVersion.get().getLanguageVersionHandler();
|
||||
LanguageVersionHandler languageVersionHandler = getLanguageVersion().getLanguageVersionHandler();
|
||||
Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions());
|
||||
|
||||
Node node;
|
||||
@ -111,8 +113,8 @@ public class ASTManager {
|
||||
|
||||
compilationUnit.setValue(node);
|
||||
lastValidSource = source;
|
||||
lastLanguageVersion = languageVersion.get();
|
||||
return compilationUnit.get();
|
||||
lastLanguageVersion = getLanguageVersion();
|
||||
return getCompilationUnit();
|
||||
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.model;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Specialises rule builders for XPath rules.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class ObservableXPathRuleBuilder extends ObservableRuleBuilder {
|
||||
|
||||
|
||||
private final Var<String> xpathVersion = Var.newSimpleVar(DesignerUtil.defaultXPathVersion());
|
||||
private final Var<String> xpathExpression = Var.newSimpleVar("");
|
||||
|
||||
|
||||
public String getXpathVersion() {
|
||||
return xpathVersion.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setXpathVersion(String xpathVersion) {
|
||||
this.xpathVersion.setValue(xpathVersion);
|
||||
}
|
||||
|
||||
|
||||
public Var<String> xpathVersionProperty() {
|
||||
return xpathVersion;
|
||||
}
|
||||
|
||||
|
||||
public String getXpathExpression() {
|
||||
return xpathExpression.getValue();
|
||||
}
|
||||
|
||||
|
||||
public Var<String> xpathExpressionProperty() {
|
||||
return xpathExpression;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Optional<Rule> build() throws IllegalArgumentException {
|
||||
return super.build(); //TODO
|
||||
}
|
||||
}
|
@ -17,10 +17,8 @@ import net.sourceforge.pmd.RuleSets;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.XPathRule;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
/**
|
||||
* Evaluates XPath expressions.
|
||||
@ -31,31 +29,22 @@ import javafx.beans.property.StringProperty;
|
||||
public class XPathEvaluator {
|
||||
|
||||
|
||||
private final StringProperty xpathVersion = new SimpleStringProperty(XPathRuleQuery.XPATH_2_0);
|
||||
|
||||
|
||||
public String getXpathVersion() {
|
||||
return xpathVersion.get();
|
||||
}
|
||||
|
||||
|
||||
public StringProperty xpathVersionProperty() {
|
||||
return xpathVersion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluates an XPath query on the compilation unit.
|
||||
*
|
||||
* @param compilationUnit AST root
|
||||
* @param languageVersion language version
|
||||
* @param xpathQuery query
|
||||
* @param xpathVersion XPath version
|
||||
* @param xpathQuery XPath query
|
||||
* @param properties Properties of the rule
|
||||
*
|
||||
* @throws XPathEvaluationException if there was an error during the evaluation. The cause is preserved
|
||||
*/
|
||||
public List<Node> evaluateQuery(Node compilationUnit,
|
||||
LanguageVersion languageVersion,
|
||||
String xpathQuery) throws XPathEvaluationException {
|
||||
String xpathVersion,
|
||||
String xpathQuery,
|
||||
List<PropertyDescriptorSpec> properties) throws XPathEvaluationException {
|
||||
|
||||
if (StringUtils.isBlank(xpathQuery)) {
|
||||
return Collections.emptyList();
|
||||
@ -71,11 +60,15 @@ public class XPathEvaluator {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
xpathRule.setMessage("");
|
||||
xpathRule.setLanguage(languageVersion.getLanguage());
|
||||
xpathRule.setXPath(xpathQuery);
|
||||
xpathRule.setVersion(xpathVersion.get());
|
||||
xpathRule.setVersion(xpathVersion);
|
||||
|
||||
properties.stream()
|
||||
.map(PropertyDescriptorSpec::build)
|
||||
.forEach(xpathRule::definePropertyDescriptor);
|
||||
|
||||
final RuleSet ruleSet = new RuleSetFactory().createSingleRuleRuleSet(xpathRule);
|
||||
|
||||
@ -94,6 +87,4 @@ public class XPathEvaluator {
|
||||
throw new XPathEvaluationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,20 +5,27 @@
|
||||
package net.sourceforge.pmd.util.fxdesigner.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.Parser;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
|
||||
/**
|
||||
@ -29,10 +36,10 @@ public class DesignerUtil {
|
||||
|
||||
|
||||
private static final Path PMD_SETTINGS_DIR = Paths.get(System.getProperty("user.home"), ".pmd");
|
||||
private static final File DESIGNER_SETTINGS_FILE = PMD_SETTINGS_DIR.resolve("pmd_new_designer.xml").toFile();
|
||||
private static final File DESIGNER_SETTINGS_FILE = PMD_SETTINGS_DIR.resolve("designer.xml").toFile();
|
||||
|
||||
|
||||
private static LanguageVersion[] supportedLanguageVersions;
|
||||
private static List<LanguageVersion> supportedLanguageVersions;
|
||||
private static Map<String, LanguageVersion> extensionsToLanguage;
|
||||
|
||||
|
||||
@ -41,28 +48,25 @@ public class DesignerUtil {
|
||||
}
|
||||
|
||||
|
||||
private static Map<String, LanguageVersion> getExtensionsToLanguageMap() {
|
||||
Map<String, LanguageVersion> result = new HashMap<>();
|
||||
Arrays.stream(getSupportedLanguageVersions())
|
||||
.map(LanguageVersion::getLanguage)
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(Language::getExtensions, Language::getDefaultVersion))
|
||||
.forEach((key, value) -> key.forEach(ext -> result.put(ext, value)));
|
||||
return result;
|
||||
public static String defaultXPathVersion() {
|
||||
return XPathRuleQuery.XPATH_2_0;
|
||||
}
|
||||
|
||||
|
||||
public static LanguageVersion getLanguageVersionFromExtension(String filename) {
|
||||
if (extensionsToLanguage == null) {
|
||||
extensionsToLanguage = getExtensionsToLanguageMap();
|
||||
}
|
||||
public static LanguageVersion defaultLanguageVersion() {
|
||||
return LanguageRegistry.getDefaultLanguage().getDefaultVersion();
|
||||
}
|
||||
|
||||
if (filename.indexOf('.') > 0) {
|
||||
String[] tokens = filename.split("\\.");
|
||||
return extensionsToLanguage.get(tokens[tokens.length - 1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* Gets the URL to an fxml file from its simple name.
|
||||
*
|
||||
* @param simpleName Simple name of the file, i.e. with no directory prefixes
|
||||
*
|
||||
* @return A URL to an fxml file
|
||||
*/
|
||||
public static URL getFxml(String simpleName) {
|
||||
return DesignerUtil.class.getResource("/net/sourceforge/pmd/util/fxdesigner/fxml/" + simpleName);
|
||||
}
|
||||
|
||||
|
||||
@ -76,20 +80,86 @@ public class DesignerUtil {
|
||||
}
|
||||
|
||||
|
||||
public static LanguageVersion[] getSupportedLanguageVersions() {
|
||||
public static <T> StringConverter<T> stringConverter(Function<T, String> toString, Function<String, T> fromString) {
|
||||
return new StringConverter<T>() {
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
return toString.apply(object);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
return fromString.apply(string);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static StringConverter<LanguageVersion> languageVersionStringConverter() {
|
||||
return DesignerUtil.stringConverter(LanguageVersion::getShortName,
|
||||
s -> LanguageRegistry.findLanguageVersionByTerseName(s.toLowerCase()));
|
||||
}
|
||||
|
||||
|
||||
private static Map<String, LanguageVersion> getExtensionsToLanguageMap() {
|
||||
Map<String, LanguageVersion> result = new HashMap<>();
|
||||
getSupportedLanguageVersions().stream()
|
||||
.map(LanguageVersion::getLanguage)
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(Language::getExtensions,
|
||||
Language::getDefaultVersion))
|
||||
.forEach((key, value) -> key.forEach(ext -> result.put(ext, value)));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static LanguageVersion getLanguageVersionFromExtension(String filename) {
|
||||
if (extensionsToLanguage == null) {
|
||||
extensionsToLanguage = getExtensionsToLanguageMap();
|
||||
}
|
||||
|
||||
if (filename.indexOf('.') > 0) {
|
||||
String[] tokens = filename.split("\\.");
|
||||
return extensionsToLanguage.get(tokens[tokens.length - 1]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static List<LanguageVersion> getSupportedLanguageVersions() {
|
||||
if (supportedLanguageVersions == null) {
|
||||
List<LanguageVersion> languageVersions = new ArrayList<>();
|
||||
for (LanguageVersion languageVersion : LanguageRegistry.findAllVersions()) {
|
||||
LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
|
||||
if (languageVersionHandler != null) {
|
||||
Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions());
|
||||
if (parser != null && parser.canParse()) {
|
||||
languageVersions.add(languageVersion);
|
||||
}
|
||||
}
|
||||
Optional.ofNullable(languageVersion.getLanguageVersionHandler())
|
||||
.map(handler -> handler.getParser(handler.getDefaultParserOptions()))
|
||||
.filter(Parser::canParse)
|
||||
.ifPresent(p -> languageVersions.add(languageVersion));
|
||||
}
|
||||
supportedLanguageVersions = languageVersions.toArray(new LanguageVersion[languageVersions.size()]);
|
||||
supportedLanguageVersions = languageVersions;
|
||||
}
|
||||
return supportedLanguageVersions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Binds the underlying property to a source of values. The source property is also initialised using the setter.
|
||||
*
|
||||
* @param underlying The underlying property
|
||||
* @param ui The property exposed to the user (the one in this wizard)
|
||||
* @param setter Setter to initialise the UI value
|
||||
* @param <T> Type of values
|
||||
*/
|
||||
public static <T> void rewire(Property<T> underlying, ObservableValue<? extends T> ui, Consumer<? super T> setter) {
|
||||
setter.accept(underlying.getValue());
|
||||
underlying.unbind();
|
||||
underlying.bind(ui); // Bindings are garbage collected after the popup dies
|
||||
}
|
||||
|
||||
/** Like rewire, with no initialisation. */
|
||||
public static <T> void rewire(Property<T> underlying, ObservableValue<? extends T> source) {
|
||||
underlying.unbind();
|
||||
underlying.bind(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptorField;
|
||||
import net.sourceforge.pmd.properties.PropertyTypeId;
|
||||
import net.sourceforge.pmd.properties.builders.PropertyDescriptorExternalBuilder;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.util.Callback;
|
||||
|
||||
|
||||
/**
|
||||
* Stores enough data to build a property descriptor, can be displayed within table views.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class PropertyDescriptorSpec implements SettingsOwner {
|
||||
|
||||
private static final String DEFAULT_STRING = "TODO";
|
||||
|
||||
private final Val<Boolean> isNumerical;
|
||||
private final Val<Boolean> isPackaged;
|
||||
private final Val<Boolean> isMultivalue;
|
||||
|
||||
private final Var<PropertyTypeId> typeId = Var.newSimpleVar(PropertyTypeId.STRING);
|
||||
private final Var<String> name = Var.newSimpleVar(DEFAULT_STRING);
|
||||
private final Var<String> value = Var.newSimpleVar(DEFAULT_STRING);
|
||||
private final Var<String> description = Var.newSimpleVar(DEFAULT_STRING);
|
||||
|
||||
|
||||
public PropertyDescriptorSpec() {
|
||||
isNumerical = typeId.map(PropertyTypeId::isPropertyNumeric);
|
||||
isPackaged = typeId.map(PropertyTypeId::isPropertyPackaged);
|
||||
isMultivalue = typeId.map(PropertyTypeId::isPropertyMultivalue);
|
||||
}
|
||||
|
||||
|
||||
public Boolean getIsNumerical() {
|
||||
return isNumerical.getValue();
|
||||
}
|
||||
|
||||
|
||||
public Val<Boolean> isNumericalProperty() {
|
||||
return isNumerical;
|
||||
}
|
||||
|
||||
|
||||
public Boolean getIsPackaged() {
|
||||
return isPackaged.getValue();
|
||||
}
|
||||
|
||||
|
||||
public Val<Boolean> isPackagedProperty() {
|
||||
return isPackaged;
|
||||
}
|
||||
|
||||
|
||||
public Boolean getIsMultivalue() {
|
||||
return isMultivalue.getValue();
|
||||
}
|
||||
|
||||
|
||||
public Val<Boolean> isMultivalueProperty() {
|
||||
return isMultivalue;
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public String getDescription() {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description.setValue(description);
|
||||
}
|
||||
|
||||
|
||||
public Var<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public PropertyTypeId getTypeId() {
|
||||
return typeId.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setTypeId(PropertyTypeId typeId) {
|
||||
this.typeId.setValue(typeId);
|
||||
}
|
||||
|
||||
|
||||
public Var<PropertyTypeId> typeIdProperty() {
|
||||
return typeId;
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public String getName() {
|
||||
return name.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setName(String name) {
|
||||
this.name.setValue(name);
|
||||
}
|
||||
|
||||
|
||||
public Var<String> nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@PersistentProperty
|
||||
public String getValue() {
|
||||
return value.getValue();
|
||||
}
|
||||
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value.setValue(value);
|
||||
}
|
||||
|
||||
|
||||
public Var<String> valueProperty() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an xml string of this property definition.
|
||||
*
|
||||
* @return An xml string
|
||||
*/
|
||||
public String toXml() {
|
||||
return String.format("<property name=\"%s\" type=\"%s\" value=\"%s\" />",
|
||||
getName(), getTypeId().getStringId(), getValue());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toXml();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the descriptor. May throw IllegalArgumentException.
|
||||
*
|
||||
* @return the descriptor if it can be built
|
||||
*/
|
||||
public PropertyDescriptor<?> build() {
|
||||
PropertyDescriptorExternalBuilder<?> externalBuilder = getTypeId().getFactory();
|
||||
Map<PropertyDescriptorField, String> values = new HashMap<>();
|
||||
values.put(PropertyDescriptorField.NAME, getName());
|
||||
values.put(PropertyDescriptorField.DEFAULT_VALUE, getValue());
|
||||
values.put(PropertyDescriptorField.DESCRIPTION, getDescription());
|
||||
|
||||
return externalBuilder.build(values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes bindings from this property spec.
|
||||
*/
|
||||
public void unbind() {
|
||||
typeIdProperty().unbind();
|
||||
nameProperty().unbind();
|
||||
descriptionProperty().unbind();
|
||||
valueProperty().unbind();
|
||||
}
|
||||
|
||||
|
||||
/** Extractor for observable lists. */
|
||||
public static Callback<PropertyDescriptorSpec, Observable[]> extractor() {
|
||||
return spec -> new Observable[]{spec.nameProperty(), spec.typeIdProperty(), spec.valueProperty()};
|
||||
}
|
||||
|
||||
|
||||
public static ObservableList<PropertyDescriptorSpec> observableList() {
|
||||
return FXCollections.observableArrayList(extractor());
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a node in the settings model. The settings model is a
|
||||
* tree of such nodes, mirroring the state hierarchy of the application.
|
||||
*
|
||||
* <p>Each node can be serialised to XML.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public abstract class BeanModelNode {
|
||||
|
||||
/** Makes the children accept the visitor. */
|
||||
public <T> void childrenAccept(BeanNodeVisitor<T> visitor, T data) {
|
||||
for (BeanModelNode child : getChildrenNodes()) {
|
||||
child.accept(visitor, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Accepts a visitor. */
|
||||
protected abstract <T> void accept(BeanNodeVisitor<T> visitor, T data);
|
||||
|
||||
|
||||
public List<? extends BeanModelNode> getChildrenNodes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentSequence;
|
||||
|
||||
|
||||
/**
|
||||
* Represents an indexed list of nodes sharing the same type.
|
||||
* This type of node is flagged with a {@link PersistentSequence},
|
||||
* which is applied to a getter of a collection.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class BeanModelNodeSeq<T extends SimpleBeanModelNode> extends BeanModelNode {
|
||||
|
||||
private final String propertyName;
|
||||
private final List<T> children = new ArrayList<>();
|
||||
|
||||
|
||||
public BeanModelNodeSeq(String name) {
|
||||
this.propertyName = name;
|
||||
}
|
||||
|
||||
|
||||
public void addChild(T node) {
|
||||
children.add(node);
|
||||
}
|
||||
|
||||
|
||||
/** Returns the elements of the sequence. */
|
||||
@Override
|
||||
public List<? extends SimpleBeanModelNode> getChildrenNodes() {
|
||||
return children;
|
||||
}
|
||||
|
||||
|
||||
/** Returns the name of the property that contains the collection. */
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
|
||||
protected <U> void accept(BeanNodeVisitor<U> visitor, U data) {
|
||||
visitor.visit(this, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
BeanModelNodeSeq<?> that = (BeanModelNodeSeq<?>) o;
|
||||
return Objects.equals(propertyName, that.propertyName)
|
||||
&& Objects.equals(children, that.children);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
return Objects.hash(propertyName, children);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
/**
|
||||
* Implements a visitor pattern over bean nodes. Used to restore properties
|
||||
* from a model and build an XML document to represent the model.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public abstract class BeanNodeVisitor<T> {
|
||||
|
||||
public void visit(BeanModelNode node, T data) {
|
||||
node.childrenAccept(this, data);
|
||||
}
|
||||
|
||||
|
||||
public void visit(BeanModelNodeSeq<?> node, T data) {
|
||||
visit((BeanModelNode) node, data);
|
||||
}
|
||||
|
||||
|
||||
public void visit(SimpleBeanModelNode node, T data) {
|
||||
visit((BeanModelNode) node, data);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
|
||||
|
||||
|
||||
/**
|
||||
* Visits a bean model and restores the properties described by the nodes
|
||||
* into their respective settings owner.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class RestorePropertyVisitor extends BeanNodeVisitor<SettingsOwner> {
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(SimpleBeanModelNode model, SettingsOwner target) {
|
||||
if (model == null) {
|
||||
return; // possibly it wasn't saved during the previous save cycle
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
if (target.getClass() != model.getNodeType()) {
|
||||
throw new IllegalArgumentException("Incorrect settings restoration target, expected "
|
||||
+ model.getNodeType() + ", actual " + target.getClass());
|
||||
}
|
||||
|
||||
Map<String, PropertyDescriptor> descriptors = Arrays.stream(PropertyUtils.getPropertyDescriptors(target))
|
||||
.filter(d -> d.getReadMethod().isAnnotationPresent(PersistentProperty.class))
|
||||
.collect(Collectors.toMap(PropertyDescriptor::getName, d -> d));
|
||||
|
||||
for (Entry<String, Object> saved : model.getSettingsValues().entrySet()) {
|
||||
if (descriptors.containsKey(saved.getKey())) {
|
||||
try {
|
||||
PropertyUtils.setProperty(target, saved.getKey(), saved.getValue());
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (BeanModelNodeSeq<?> seq : model.getSequenceProperties()) {
|
||||
this.visit(seq, target);
|
||||
}
|
||||
|
||||
for (SettingsOwner child : target.getChildrenSettingsNodes()) {
|
||||
model.getChildrenByType().get(child.getClass()).accept(this, child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(BeanModelNodeSeq<?> model, SettingsOwner target) {
|
||||
if (model == null) {
|
||||
return; // possibly it wasn't saved during the previous save cycle
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
Collection<SettingsOwner> container;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<SettingsOwner> tmp = (Collection<SettingsOwner>) PropertyUtils.getProperty(target, model.getPropertyName());
|
||||
container = tmp;
|
||||
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Iterator<SettingsOwner> existingItems = container.iterator();
|
||||
Class<?> itemType = null;
|
||||
for (SimpleBeanModelNode child : model.getChildrenNodes()) {
|
||||
SettingsOwner item;
|
||||
if (existingItems.hasNext()) {
|
||||
item = existingItems.next();
|
||||
} else {
|
||||
if (itemType == null) {
|
||||
itemType = child.getNodeType();
|
||||
}
|
||||
|
||||
try {
|
||||
item = (SettingsOwner) itemType.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
continue; // try hard
|
||||
}
|
||||
}
|
||||
|
||||
child.accept(this, item);
|
||||
container.add(item);
|
||||
}
|
||||
|
||||
try {
|
||||
PropertyUtils.setProperty(target, model.getPropertyName(), container);
|
||||
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Marker interface for settings owners. Settings owners form a
|
||||
* tree-like hierarchy, which is explored recursively to build
|
||||
* a model of the settings to persist, under the form of a
|
||||
* {@link SimpleBeanModelNode}.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public interface SettingsOwner {
|
||||
|
||||
|
||||
/** Gets the children of this node in order. */
|
||||
default List<SettingsOwner> getChildrenSettingsNodes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.commons.beanutils.ConvertUtils;
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import net.sourceforge.pmd.RulePriority;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.properties.PropertyTypeId;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.LanguageVersionConverter;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.PropertyTypeIdConverter;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.RulePriorityConverter;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods to persist settings of the application.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @see SimpleBeanModelNode
|
||||
* @see SettingsOwner
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class SettingsPersistenceUtil {
|
||||
|
||||
static {
|
||||
// register converters for custom types
|
||||
ConvertUtils.register(new RulePriorityConverter(), RulePriority.class);
|
||||
ConvertUtils.register(new PropertyTypeIdConverter(), PropertyTypeId.class);
|
||||
ConvertUtils.register(new LanguageVersionConverter(), LanguageVersion.class);
|
||||
}
|
||||
|
||||
|
||||
private SettingsPersistenceUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores properties contained in the file into the given object.
|
||||
*
|
||||
* @param root Root of the hierarchy
|
||||
* @param file Properties file
|
||||
*/
|
||||
public static void restoreProperties(SettingsOwner root, File file) {
|
||||
Optional<Document> odoc = getDocument(file);
|
||||
|
||||
odoc.flatMap(XmlFormatRevision::getSuitableReader)
|
||||
.map(rev -> rev.xmlInterface)
|
||||
.flatMap(xmlInterface -> odoc.flatMap(xmlInterface::parseXml))
|
||||
.ifPresent(n -> restoreSettings(root, n));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save properties of this object and descendants into the given file.
|
||||
*
|
||||
* @param root Root of the hierarchy
|
||||
* @param file Properties file
|
||||
*/
|
||||
public static void persistProperties(SettingsOwner root, File file) throws IOException {
|
||||
SimpleBeanModelNode node = SettingsPersistenceUtil.buildSettingsModel(root);
|
||||
XmlFormatRevision.getLatest().xmlInterface.writeModelToXml(file, node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an XML document for the given file if it exists and can be parsed.
|
||||
*
|
||||
* @param file File to parse
|
||||
*/
|
||||
private static Optional<Document> getDocument(File file) {
|
||||
InputStream stream = null;
|
||||
if (file.exists()) {
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
stream = new FileInputStream(file);
|
||||
Document document = builder.parse(stream);
|
||||
return Optional.of(document);
|
||||
} catch (SAXException | ParserConfigurationException | IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a settings model recursively for the given settings owner.
|
||||
* The properties which have a getter tagged with {@link PersistentProperty}
|
||||
* are retrieved for later serialisation.
|
||||
*
|
||||
* @param root The root of the settings owner hierarchy.
|
||||
*
|
||||
* @return The built model
|
||||
*/
|
||||
// test only
|
||||
static SimpleBeanModelNode buildSettingsModel(SettingsOwner root) {
|
||||
SimpleBeanModelNode node = new SimpleBeanModelNode(root.getClass());
|
||||
|
||||
for (PropertyDescriptor d : PropertyUtils.getPropertyDescriptors(root)) {
|
||||
if (d.getReadMethod() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (d.getReadMethod().isAnnotationPresent(PersistentSequence.class)) {
|
||||
|
||||
Object val = d.getReadMethod().invoke(root);
|
||||
if (!Collection.class.isAssignableFrom(val.getClass())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<SettingsOwner> values = (Collection<SettingsOwner>) val;
|
||||
|
||||
BeanModelNodeSeq<SimpleBeanModelNode> seq = new BeanModelNodeSeq<>(d.getName());
|
||||
|
||||
for (SettingsOwner item : values) {
|
||||
seq.addChild(buildSettingsModel(item));
|
||||
}
|
||||
|
||||
node.addChild(seq);
|
||||
} else if (d.getReadMethod().isAnnotationPresent(PersistentProperty.class)) {
|
||||
node.addProperty(d.getName(), d.getReadMethod().invoke(root), d.getPropertyType());
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (SettingsOwner child : root.getChildrenSettingsNodes()) {
|
||||
node.addChild(buildSettingsModel(child));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores the settings from the model into the target. Dual of
|
||||
* {@link #buildSettingsModel(SettingsOwner)}. Traverses all the
|
||||
* tree.
|
||||
*
|
||||
* @param target Object in which to restore the properties
|
||||
* @param model The model
|
||||
*/
|
||||
// test only
|
||||
static void restoreSettings(SettingsOwner target, BeanModelNode model) {
|
||||
if (model == null) {
|
||||
return; // possibly it wasn't saved during the previous save cycle
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
model.accept(new RestorePropertyVisitor(), target);
|
||||
}
|
||||
|
||||
|
||||
/** Enumerates different formats for compatibility. */
|
||||
private enum XmlFormatRevision implements Comparable<XmlFormatRevision> {
|
||||
V1(new XmlInterfaceVersion1(1));
|
||||
|
||||
private final XmlInterface xmlInterface;
|
||||
|
||||
|
||||
XmlFormatRevision(XmlInterface xmlI) {
|
||||
this.xmlInterface = xmlI;
|
||||
}
|
||||
|
||||
|
||||
public static XmlFormatRevision getLatest() {
|
||||
return Arrays.stream(values()).max(Comparator.comparingInt(x -> x.xmlInterface.getRevisionNumber())).get();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a handler capable of reading the given document.
|
||||
*
|
||||
* @param doc The revision number
|
||||
*
|
||||
* @return A handler, if it can be found
|
||||
*/
|
||||
public static Optional<XmlFormatRevision> getSuitableReader(Document doc) {
|
||||
return Arrays.stream(values())
|
||||
.filter(rev -> rev.xmlInterface.canParse(doc))
|
||||
.findAny();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tags the *getter* of a property as suitable for persistence.
|
||||
* The property will be serialized and restored on startup, so
|
||||
* it must have a setter.
|
||||
*
|
||||
* <p>Properties setters and getters must respect JavaBeans
|
||||
* conventions.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface PersistentProperty {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tags the getter of a collection for persistence. The collection
|
||||
* elements must implement {@link SettingsOwner} and have a noargs
|
||||
* constructor. This is a solution to the problem of serializing
|
||||
* collections of items of arbitrary complexity.
|
||||
*
|
||||
* <p>When restoring such a property, we assume that the property
|
||||
* already has a value, and we either update existing items with
|
||||
* the properties or we instantiate new items if not enough are
|
||||
* already available.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface PersistentSequence {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentSequence;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a node in the settings owner tree, and stores the values of the properties that
|
||||
* should be saved and restored. A node can have other nodes as children, in which case they are
|
||||
* identified using their type at restore time. To persist the properties of multiple children with
|
||||
* the same type, see {@link PersistentSequence} and {@link BeanModelNodeSeq}.
|
||||
*
|
||||
* <p>This intermediary representation decouples the XML representation from the business logic,
|
||||
* allowing several parsers / serializers to coexist for different versions of the schema.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class SimpleBeanModelNode extends BeanModelNode {
|
||||
|
||||
|
||||
private final Class<?> nodeType;
|
||||
|
||||
private final Map<String, Object> propertyValues = new HashMap<>();
|
||||
private final Map<String, Class<?>> propertyTypes = new HashMap<>();
|
||||
private final Map<Class<?>, BeanModelNode> children = new HashMap<>();
|
||||
private final Set<BeanModelNodeSeq<?>> sequenceProperties = new HashSet<>();
|
||||
|
||||
|
||||
public SimpleBeanModelNode(Class<?> nodeType) {
|
||||
this.nodeType = nodeType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add one more property with its value.
|
||||
*
|
||||
* @param propertyKey Unique name identifying the property.
|
||||
* @param value Value
|
||||
* @param type Type of the property
|
||||
*/
|
||||
public void addProperty(String propertyKey, Object value, Class<?> type) {
|
||||
propertyValues.put(propertyKey, value);
|
||||
propertyTypes.put(propertyKey, type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a sequence of nodes as a child of this node.
|
||||
*
|
||||
* @param seq Sequence of nodes
|
||||
*/
|
||||
public void addChild(BeanModelNodeSeq<?> seq) {
|
||||
sequenceProperties.add(seq);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a node to the children of this node.
|
||||
*
|
||||
* @param child Node
|
||||
*/
|
||||
public void addChild(SimpleBeanModelNode child) {
|
||||
children.put(child.nodeType, child);
|
||||
}
|
||||
|
||||
|
||||
/** Returns a map of property names to their value. */
|
||||
public Map<String, Object> getSettingsValues() {
|
||||
return Collections.unmodifiableMap(propertyValues);
|
||||
}
|
||||
|
||||
|
||||
/** Returns a map of property names to their type. */
|
||||
public Map<String, Class<?>> getSettingsTypes() {
|
||||
return Collections.unmodifiableMap(propertyTypes);
|
||||
}
|
||||
|
||||
|
||||
/** Returns a map of children by type. */
|
||||
public Map<Class<?>, BeanModelNode> getChildrenByType() {
|
||||
return Collections.unmodifiableMap(children);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<? extends BeanModelNode> getChildrenNodes() {
|
||||
Set<BeanModelNode> allChildren = new HashSet<>(children.values());
|
||||
allChildren.addAll(sequenceProperties);
|
||||
return new ArrayList<>(allChildren);
|
||||
}
|
||||
|
||||
|
||||
/** Gets the sequences of nodes registered as children. */
|
||||
public Set<BeanModelNodeSeq<?>> getSequenceProperties() {
|
||||
return sequenceProperties;
|
||||
}
|
||||
|
||||
|
||||
/** Get the type of the settings owner represented by this node. */
|
||||
public Class<?> getNodeType() {
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected <U> void accept(BeanNodeVisitor<U> visitor, U data) {
|
||||
visitor.visit(this, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SimpleBeanModelNode that = (SimpleBeanModelNode) o;
|
||||
return Objects.equals(nodeType, that.nodeType)
|
||||
&& Objects.equals(propertyValues, that.propertyValues)
|
||||
&& Objects.equals(propertyTypes, that.propertyTypes)
|
||||
&& Objects.equals(children, that.children)
|
||||
&& Objects.equals(sequenceProperties, that.sequenceProperties);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(nodeType, propertyValues, propertyTypes, children, sequenceProperties);
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a version of the Xml format used to store settings. The
|
||||
* parser and serializer must understand each other, so they're kept
|
||||
* together.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public abstract class XmlInterface {
|
||||
|
||||
// modifying these will break compatibility
|
||||
private static final String SCHEMA_MODEL_VERSION = "revision";
|
||||
private static final String SCHEMA_DOCUMENT_ELEMENT = "designer-settings";
|
||||
|
||||
private final int revisionNumber;
|
||||
|
||||
|
||||
public XmlInterface(int rev) {
|
||||
this.revisionNumber = rev;
|
||||
}
|
||||
|
||||
|
||||
public int getRevisionNumber() {
|
||||
return revisionNumber;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a XML document produced by {@link #writeModelToXml(File, SimpleBeanModelNode)}
|
||||
* into a settings node.
|
||||
*
|
||||
* @param document The document to parse
|
||||
*
|
||||
* @return The root of the model hierarchy, or empty if the revision is not supported
|
||||
*/
|
||||
public final Optional<SimpleBeanModelNode> parseXml(Document document) {
|
||||
if (canParse(document)) {
|
||||
Element rootNodeElement = (Element) document.getDocumentElement().getChildNodes().item(1);
|
||||
return Optional.ofNullable(parseSettingsOwnerNode(rootNodeElement));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the document can be read by this object.
|
||||
*
|
||||
* @param document Document to test
|
||||
*/
|
||||
public boolean canParse(Document document) {
|
||||
int docVersion = Integer.parseInt(document.getDocumentElement().getAttribute(SCHEMA_MODEL_VERSION));
|
||||
return docVersion == getRevisionNumber();
|
||||
}
|
||||
|
||||
|
||||
private Document initDocument() throws IOException {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder documentBuilder;
|
||||
try {
|
||||
documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new IOException("Failed to create settings document builder", e);
|
||||
}
|
||||
Document document = documentBuilder.newDocument();
|
||||
|
||||
Element settingsElement = document.createElement(SCHEMA_DOCUMENT_ELEMENT);
|
||||
settingsElement.setAttribute(SCHEMA_MODEL_VERSION, "" + getRevisionNumber());
|
||||
document.appendChild(settingsElement);
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
/** Saves parameters to disk. */
|
||||
private void save(Document document, File outputFile) throws IOException {
|
||||
try {
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
|
||||
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
|
||||
|
||||
Source source = new DOMSource(document);
|
||||
outputFile.getParentFile().mkdirs();
|
||||
Result result = new StreamResult(new FileWriter(outputFile));
|
||||
transformer.transform(source, result);
|
||||
} catch (TransformerException e) {
|
||||
throw new IOException("Failed to save settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the model to a file.
|
||||
*
|
||||
* @param output The output file
|
||||
* @param model The model to serialize
|
||||
*
|
||||
* @throws IOException If saving the settings failed
|
||||
*/
|
||||
public final void writeModelToXml(File output, SimpleBeanModelNode model) throws IOException {
|
||||
Document document = initDocument();
|
||||
model.accept(getDocumentMakerVisitor(), document.getDocumentElement());
|
||||
save(document, output);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a settings node and its descendants recursively.
|
||||
*
|
||||
* @param nodeElement Element to parse
|
||||
*
|
||||
* @return The model described by the element
|
||||
*/
|
||||
protected abstract SimpleBeanModelNode parseSettingsOwnerNode(Element nodeElement);
|
||||
|
||||
|
||||
/**
|
||||
* Gets a visitor which populates xml elements with corresponding nodes.
|
||||
*
|
||||
* @return A visitor
|
||||
*/
|
||||
protected abstract BeanNodeVisitor<Element> getDocumentMakerVisitor();
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.beanutils.ConvertUtils;
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
|
||||
/**
|
||||
* V0, really.
|
||||
*
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class XmlInterfaceVersion1 extends XmlInterface {
|
||||
|
||||
|
||||
// names used in the Xml schema
|
||||
private static final String SCHEMA_NODE_ELEMENT = "node";
|
||||
private static final String SCHEMA_NODESEQ_ELEMENT = "nodeseq";
|
||||
private static final String SCHEMA_NODE_CLASS_ATTRIBUTE = "class";
|
||||
private static final String SCHEMA_PROPERTY_ELEMENT = "property";
|
||||
private static final String SCHEMA_PROPERTY_NAME = "name";
|
||||
private static final String SCHEMA_PROPERTY_TYPE = "type";
|
||||
|
||||
|
||||
public XmlInterfaceVersion1(int revisionNumber) {
|
||||
super(revisionNumber);
|
||||
}
|
||||
|
||||
|
||||
private List<Element> getChildrenByTagName(Element element, String tagName) {
|
||||
NodeList children = element.getChildNodes();
|
||||
List<Element> elts = new ArrayList<>();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
if (children.item(i).getNodeType() == Node.ELEMENT_NODE && tagName.equals(children.item(i).getNodeName())) {
|
||||
elts.add((Element) children.item(i));
|
||||
}
|
||||
}
|
||||
|
||||
return elts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected SimpleBeanModelNode parseSettingsOwnerNode(Element nodeElement) {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(nodeElement.getAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE));
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SimpleBeanModelNode node = new SimpleBeanModelNode(clazz);
|
||||
|
||||
for (Element setting : getChildrenByTagName(nodeElement, SCHEMA_PROPERTY_ELEMENT)) {
|
||||
parseSingleProperty(setting, node);
|
||||
}
|
||||
|
||||
for (Element child : getChildrenByTagName(nodeElement, SCHEMA_NODE_ELEMENT)) {
|
||||
try {
|
||||
if (node.getChildrenByType().get(Class.forName(child.getAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE))) == null) { // FIXME
|
||||
node.addChild(parseSettingsOwnerNode(child));
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
for (Element seq : getChildrenByTagName(nodeElement, SCHEMA_NODESEQ_ELEMENT)) {
|
||||
parseNodeSeq(seq, node);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
private void parseSingleProperty(Element propertyElement, SimpleBeanModelNode owner) {
|
||||
String typeName = propertyElement.getAttribute(SCHEMA_PROPERTY_TYPE);
|
||||
String name = propertyElement.getAttribute(SCHEMA_PROPERTY_NAME);
|
||||
Class<?> type;
|
||||
try {
|
||||
type = ClassUtils.getClass(typeName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
ConvertUtils.convert(new Object());
|
||||
Object value = ConvertUtils.convert(propertyElement.getTextContent(), type);
|
||||
|
||||
owner.addProperty(name, value, type);
|
||||
}
|
||||
|
||||
|
||||
private void parseNodeSeq(Element nodeSeq, SimpleBeanModelNode parent) {
|
||||
BeanModelNodeSeq<SimpleBeanModelNode> built = new BeanModelNodeSeq<>(nodeSeq.getAttribute(SCHEMA_PROPERTY_NAME));
|
||||
for (Element child : getChildrenByTagName(nodeSeq, SCHEMA_NODE_ELEMENT)) {
|
||||
built.addChild(parseSettingsOwnerNode(child));
|
||||
}
|
||||
parent.addChild(built);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected BeanNodeVisitor<Element> getDocumentMakerVisitor() {
|
||||
return new DocumentMakerVisitor();
|
||||
}
|
||||
|
||||
|
||||
public static class DocumentMakerVisitor extends BeanNodeVisitor<Element> {
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(SimpleBeanModelNode node, Element parent) {
|
||||
Element nodeElement = parent.getOwnerDocument().createElement(SCHEMA_NODE_ELEMENT);
|
||||
nodeElement.setAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE, node.getNodeType().getCanonicalName());
|
||||
|
||||
for (Entry<String, Object> keyValue : node.getSettingsValues().entrySet()) {
|
||||
// I don't think the API is intended to be used like that
|
||||
// but ConvertUtils wouldn't use the convertToString methods
|
||||
// defined in the converters otherwise.
|
||||
// Even when a built-in converter is available, objects are
|
||||
// still converted with Object::toString which fucks up the
|
||||
// conversion...
|
||||
String value = (String) ConvertUtils.lookup(keyValue.getValue().getClass()).convert(String.class, keyValue.getValue());
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Element setting = parent.getOwnerDocument().createElement(SCHEMA_PROPERTY_ELEMENT);
|
||||
setting.setAttribute(SCHEMA_PROPERTY_NAME, keyValue.getKey());
|
||||
setting.setAttribute(SCHEMA_PROPERTY_TYPE, node.getSettingsTypes().get(keyValue.getKey()).getCanonicalName());
|
||||
setting.appendChild(parent.getOwnerDocument().createCDATASection(value));
|
||||
nodeElement.appendChild(setting);
|
||||
}
|
||||
|
||||
parent.appendChild(nodeElement);
|
||||
super.visit(node, nodeElement);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(BeanModelNodeSeq<?> node, Element parent) {
|
||||
Element nodeElement = parent.getOwnerDocument().createElement(SCHEMA_NODESEQ_ELEMENT);
|
||||
nodeElement.setAttribute(SCHEMA_PROPERTY_NAME, node.getPropertyName());
|
||||
parent.appendChild(nodeElement);
|
||||
super.visit(node, nodeElement);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans.converters;
|
||||
|
||||
import org.apache.commons.beanutils.converters.AbstractConverter;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class LanguageVersionConverter extends AbstractConverter {
|
||||
|
||||
@Override
|
||||
protected String convertToString(Object value) {
|
||||
return ((LanguageVersion) value).getTerseName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object convertToType(Class aClass, Object o) {
|
||||
return LanguageRegistry.findLanguageVersionByTerseName(o.toString());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Class getDefaultType() {
|
||||
return LanguageVersion.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans.converters;
|
||||
|
||||
import org.apache.commons.beanutils.converters.AbstractConverter;
|
||||
|
||||
import net.sourceforge.pmd.properties.PropertyTypeId;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class PropertyTypeIdConverter extends AbstractConverter {
|
||||
|
||||
@Override
|
||||
protected String convertToString(Object value) {
|
||||
return ((PropertyTypeId) value).getStringId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object convertToType(Class aClass, Object o) {
|
||||
return PropertyTypeId.lookupMnemonic(o.toString());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Class getDefaultType() {
|
||||
return PropertyTypeId.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.beans.converters;
|
||||
|
||||
import org.apache.commons.beanutils.converters.AbstractConverter;
|
||||
|
||||
import net.sourceforge.pmd.RulePriority;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public class RulePriorityConverter extends AbstractConverter {
|
||||
|
||||
@Override
|
||||
protected String convertToString(Object value) {
|
||||
return Integer.toString(((RulePriority) value).getPriority());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object convertToType(Class aClass, Object o) {
|
||||
return RulePriority.valueOf(Integer.parseInt(o.toString()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Class getDefaultType() {
|
||||
return RulePriority.class;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.Java
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XPathSyntaxHighlighter;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XmlSyntaxHighlighter;
|
||||
|
||||
|
||||
/**
|
||||
* Lists the available syntax highlighter engines by language.
|
||||
*
|
||||
@ -40,21 +41,17 @@ public enum AvailableSyntaxHighlighters {
|
||||
|
||||
|
||||
/**
|
||||
* Gets the highlighter for a language if available, otherwise returns null.
|
||||
* Gets the highlighter for a language if available.
|
||||
*
|
||||
* @param language Language to look for
|
||||
*
|
||||
* @return A highlighter if available, otherwise null
|
||||
* @return A highlighter, if available
|
||||
*/
|
||||
public static SyntaxHighlighter getComputerForLanguage(Language language) {
|
||||
Optional<AvailableSyntaxHighlighters> found = Arrays.stream(AvailableSyntaxHighlighters.values())
|
||||
.filter(e -> e.language.equals(language.getTerseName()))
|
||||
.findFirst();
|
||||
if (found.isPresent()) {
|
||||
return found.get().engine;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public static Optional<SyntaxHighlighter> getHighlighterForLanguage(Language language) {
|
||||
return Arrays.stream(AvailableSyntaxHighlighters.values())
|
||||
.filter(e -> e.language.equals(language.getTerseName()))
|
||||
.findFirst()
|
||||
.map(h -> h.engine);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ public class CustomCodeArea extends CodeArea {
|
||||
|
||||
try { // refresh the highlighting.
|
||||
Task<StyleSpans<Collection<String>>> t = computeHighlightingAsync(this.getText());
|
||||
t.setOnSucceeded((e) -> {
|
||||
t.setOnSucceeded(e -> {
|
||||
StyleLayer layer = styleContext.getLayer(SYNTAX_HIGHLIGHT_LAYER_ID);
|
||||
layer.reset(t.getValue());
|
||||
this.paintCss();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user