Beanify controllers, new settings persistence logic

Add property edition to the XPath panel
This commit is contained in:
Clément Fournier
2017-12-21 16:45:24 +01:00
parent b25e68d907
commit bc95ca803c
47 changed files with 2923 additions and 864 deletions

View File

@ -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);

View File

@ -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>

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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

View File

@ -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()
);
}

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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 {
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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