Document and organise code

This commit is contained in:
Clément Fournier
2019-01-28 22:42:01 +01:00
parent 8759db46c4
commit 3dc99bc160
26 changed files with 324 additions and 233 deletions

View File

@ -11,6 +11,7 @@ import java.util.Objects;
import java.util.stream.Collectors;
import net.sourceforge.pmd.PMDVersion;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import javafx.application.Application;
@ -23,7 +24,7 @@ import javafx.stage.Stage;
/**
* Main class for the designer.
* Main class for the designer, launched only if {@link DesignerStarter} detected JavaFX support.
*
* @author Clément Fournier
* @since 6.0.0
@ -45,13 +46,12 @@ public class Designer extends Application {
@Override
public void start(Stage stage) throws IOException {
boolean developerMode = parseParameters(getParameters());
boolean isDeveloperMode = parseParameters(getParameters());
FXMLLoader loader
= new FXMLLoader(DesignerUtil.getFxml("designer.fxml"));
FXMLLoader loader = new FXMLLoader(DesignerUtil.getFxml("designer.fxml"));
DesignerRoot owner = new DesignerRoot(stage, developerMode);
DesignerRoot owner = new DesignerRoot(stage, isDeveloperMode);
MainDesignerController mainController = new MainDesignerController(owner);
NodeInfoPanelController nodeInfoPanelController = new NodeInfoPanelController(mainController);

View File

@ -23,14 +23,14 @@ import org.reactfx.value.Val;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
import net.sourceforge.pmd.util.fxdesigner.popups.EventLogController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.ApplicationComponent;
import net.sourceforge.pmd.util.fxdesigner.util.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.LimitedSizeStack;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.SoftReferenceCache;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil;
@ -67,12 +67,7 @@ import javafx.stage.FileChooser;
* @since 6.0.0
*/
@SuppressWarnings("PMD.UnusedPrivateField")
public class MainDesignerController extends AbstractController implements CompositeSelectionSource, ApplicationComponent {
/**
* Callback to the owner.
*/
private final DesignerRoot designerRoot;
public class MainDesignerController extends AbstractController<AbstractController<?>> implements CompositeSelectionSource {
/* Menu bar */
@ -115,16 +110,11 @@ public class MainDesignerController extends AbstractController implements Compos
public MainDesignerController(DesignerRoot owner) {
this.designerRoot = owner;
super(owner, null);
eventLogController = new SoftReferenceCache<>(() -> new EventLogController(this));
}
@Override
public DesignerRoot getDesignerRoot() {
return designerRoot;
}
@Override
protected void beforeParentInit() {
@ -142,7 +132,7 @@ public class MainDesignerController extends AbstractController implements Compos
openRecentMenu.setOnShowing(e -> updateRecentFilesMenu());
fileMenu.setOnShowing(e -> onFileMenuShowing());
setupAuxclasspathMenuItem.setOnAction(e -> sourceEditorController.showAuxclasspathSetupPopup(designerRoot));
setupAuxclasspathMenuItem.setOnAction(e -> sourceEditorController.showAuxclasspathSetupPopup());
openEventLogMenuItem.setOnAction(e -> eventLogController.getValue().showPopup());
openEventLogMenuItem.textProperty().bind(
@ -163,12 +153,12 @@ public class MainDesignerController extends AbstractController implements Compos
// the xpath panel is forwarded to the treeView, which
// forwards back an event, etc.
getSelectionEvents().thenIgnoreFor(Duration.ofMillis(20))
.subscribe(n -> CompositeSelectionSource.super.select(n));
.subscribe(n -> CompositeSelectionSource.super.bubbleDown(n));
}
@Override
public ObservableSet<? extends NodeSelectionSource> getComponents() {
public ObservableSet<? extends NodeSelectionSource> getSubSelectionSources() {
return FXCollections.observableSet(nodeInfoPanelController, sourceEditorController, xpathPanelController);
}
@ -298,7 +288,7 @@ public class MainDesignerController extends AbstractController implements Compos
private void onOpenFileClicked() {
FileChooser chooser = new FileChooser();
chooser.setTitle("Load source from file");
File file = chooser.showOpenDialog(designerRoot.getMainStage());
File file = chooser.showOpenDialog(getMainStage());
loadSourceFromFile(file);
}
@ -396,13 +386,13 @@ public class MainDesignerController extends AbstractController implements Compos
@PersistentProperty
public boolean isMaximized() {
return designerRoot.getMainStage().isMaximized();
return getMainStage().isMaximized();
}
public void setMaximized(boolean b) {
designerRoot.getMainStage().setMaximized(!b); // trigger change listener anyway
designerRoot.getMainStage().setMaximized(b);
getMainStage().setMaximized(!b); // trigger change listener anyway
getMainStage().setMaximized(b);
}
@ -420,7 +410,7 @@ public class MainDesignerController extends AbstractController implements Compos
@Override
public List<AbstractController> getChildren() {
public List<AbstractController<MainDesignerController>> getChildren() {
return Arrays.asList(xpathPanelController, sourceEditorController, nodeInfoPanelController);
}

View File

@ -30,8 +30,8 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.lang.symboltable.Scope;
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeItem;
@ -57,9 +57,8 @@ import javafx.scene.control.TreeView;
* @since 6.0.0
*/
@SuppressWarnings("PMD.UnusedPrivateField")
public class NodeInfoPanelController extends AbstractController implements NodeSelectionSource {
public class NodeInfoPanelController extends AbstractController<MainDesignerController> implements NodeSelectionSource {
private final MainDesignerController parent;
/** List of attribute names that are ignored if {@link #isHideCommonAttributes()} is true. */
private static final List<String> IGNORABLE_ATTRIBUTES =
@ -85,15 +84,10 @@ public class NodeInfoPanelController extends AbstractController implements NodeS
private Node selectedNode;
public NodeInfoPanelController(MainDesignerController mainController) {
parent = mainController;
super(mainController);
}
@Override
public DesignerRoot getDesignerRoot() {
return parent.getDesignerRoot();
}
@Override
protected void beforeParentInit() {

View File

@ -32,10 +32,10 @@ import net.sourceforge.pmd.util.ClasspathClassLoader;
import net.sourceforge.pmd.util.fxdesigner.model.ASTManager;
import net.sourceforge.pmd.util.fxdesigner.model.ParseAbortedException;
import net.sourceforge.pmd.util.fxdesigner.popups.AuxclasspathSetupController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlighters;
@ -62,7 +62,7 @@ import javafx.scene.control.ToggleGroup;
* @author Clément Fournier
* @since 6.0.0
*/
public class SourceEditorController extends AbstractController implements CompositeSelectionSource {
public class SourceEditorController extends AbstractController<MainDesignerController> implements CompositeSelectionSource {
private static final Duration AST_REFRESH_DELAY = Duration.ofMillis(100);
@ -81,8 +81,6 @@ public class SourceEditorController extends AbstractController implements Compos
private final ASTManager astManager;
private final MainDesignerController parent;
private final Var<Node> currentFocusNode = Var.newSimpleVar(null);
private final Var<List<File>> auxclasspathFiles = Var.newSimpleVar(emptyList());
@ -99,18 +97,12 @@ public class SourceEditorController extends AbstractController implements Compos
public SourceEditorController(MainDesignerController mainController) {
parent = mainController;
super(mainController);
astManager = new ASTManager(mainController.getDesignerRoot());
}
@Override
public DesignerRoot getDesignerRoot() {
return parent.getDesignerRoot();
}
@Override
protected void beforeParentInit() {
@ -200,7 +192,7 @@ public class SourceEditorController extends AbstractController implements Compos
@Override
public ObservableSet<? extends NodeSelectionSource> getComponents() {
public ObservableSet<? extends NodeSelectionSource> getSubSelectionSources() {
return FXCollections.observableSet(astTreeView, focusNodeParentageCrumbBar);
}
@ -230,10 +222,9 @@ public class SourceEditorController extends AbstractController implements Compos
}
public void showAuxclasspathSetupPopup(DesignerRoot root) {
new AuxclasspathSetupController(root).show(root.getMainStage(),
auxclasspathFiles.getValue(),
auxclasspathFiles::setValue);
public void showAuxclasspathSetupPopup() {
new AuxclasspathSetupController(getDesignerRoot())
.show(getMainStage(), auxclasspathFiles.getValue(), auxclasspathFiles::setValue);
}
private void setUpToDateCompilationUnit(Node node) {

View File

@ -27,14 +27,14 @@ 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.Category;
import net.sourceforge.pmd.util.fxdesigner.app.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.popups.ExportXPathWizardController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.CompletionResultSource;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.XPathAutocompleteProvider;
@ -75,10 +75,9 @@ import javafx.stage.StageStyle;
* @see ExportXPathWizardController
* @since 6.0.0
*/
public class XPathPanelController extends AbstractController implements NodeSelectionSource {
public class XPathPanelController extends AbstractController<MainDesignerController> implements NodeSelectionSource {
private static final Duration XPATH_REFRESH_DELAY = Duration.ofMillis(100);
private final MainDesignerController parent;
private final XPathEvaluator xpathEvaluator = new XPathEvaluator();
private final ObservableXPathRuleBuilder ruleBuilder = new ObservableXPathRuleBuilder();
@ -103,16 +102,11 @@ public class XPathPanelController extends AbstractController implements NodeSele
public XPathPanelController(MainDesignerController mainController) {
parent = mainController;
super(mainController);
getRuleBuilder().setClazz(XPathRule.class);
}
@Override
public DesignerRoot getDesignerRoot() {
return parent.getDesignerRoot();
}
@Override
protected void beforeParentInit() {
xpathExpressionArea.setSyntaxHighlighter(new XPathSyntaxHighlighter());

View File

@ -2,7 +2,7 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
package net.sourceforge.pmd.util.fxdesigner.app;
import java.net.URL;
import java.util.Collections;
@ -16,24 +16,62 @@ import javafx.fxml.Initializable;
/**
* Make the initialization cycle of JavaFX clearer. Children controller
* are initialized before their parent, but sometimes it should only
* Base class for controllers of the app. The main window of the app is split
* into regions corresponding to some area of functionality. Each has its own
* FXML file that can be found in the fxml resource directory. Each also has
* its own independent controller. Since the FXML regions are nested like a
* tree (the JavaFX scene graph), it's natural to link the controllers in a
* tree too.
*
* <p>For now controllers mostly communicate by sending messages to their parent
* and letting it forward the message to the rest of the app. TODO I'm more and more
* convinced we should avoid that and stop having the controllers hold a reference
* to their parent. They should only communicate by exposing properties their parent
* binds to, but they shouldn't know about their parent.
*
* <p>This class mainly to make the initialization cycle of JavaFX clearer. Children controllers
* are initialized before their parent, but sometimes they should only
* perform some actions after its parent has been initialized, e.g. binding
* properties that depend on a restored setting or stuff. This is part
* of the reason why {@link Platform#runLater(Runnable)} can sometimes
* be enough to solve initialization problems.
*
* This only works if all controllers in the tree extend this class.
* <p>This only works if all controllers in the initialization sequence of an
* FXML file extend this class.
*
*
* @param <T> Type of the parent controller
*
* @author Clément Fournier
* @since 7.0.0
*/
public abstract class AbstractController implements Initializable, SettingsOwner, ApplicationComponent {
public abstract class AbstractController<T extends AbstractController<?>> implements Initializable, SettingsOwner, ApplicationComponent {
protected final T parent;
private final DesignerRoot designerRoot;
protected AbstractController(DesignerRoot root, T parent) {
this.parent = parent;
this.designerRoot = root;
}
protected AbstractController(T parent) {
this(parent.getDesignerRoot(), parent);
}
@Override
public DesignerRoot getDesignerRoot() {
return designerRoot;
}
@Override
public final void initialize(URL url, ResourceBundle resourceBundle) {
beforeParentInit();
for (AbstractController child : getChildren()) {
for (AbstractController<?> child : getChildren()) {
child.afterParentInit();
}
afterChildrenInit();
@ -76,7 +114,7 @@ public abstract class AbstractController implements Initializable, SettingsOwner
}
protected List<? extends AbstractController> getChildren() {
protected List<? extends AbstractController<?>> getChildren() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,130 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.app;
import java.util.function.Supplier;
import net.sourceforge.pmd.util.fxdesigner.SourceEditorController;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource.NodeSelectionEvent;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner;
import net.sourceforge.pmd.util.fxdesigner.util.controls.AstTreeView;
import javafx.scene.control.Control;
import javafx.stage.Stage;
/**
* Some part of the application, e.g. a controller. Components in an instance of the app are all linked
* to the same {@link DesignerRoot}, which hosts utilities globally available to the app, e.g. the logger.
*
* <p>Components that are not controllers, e.g. {@link Control}s, should be injected with the designer
* root at initialization time, eg what {@link SourceEditorController} does with {@link AstTreeView}.
*
* <p>Some more specific cross-cutting structures for the internals of the app are the {@link SettingsOwner}
* tree, which is more or less identical to the {@link AbstractController} tree. {@link NodeSelectionSource}s
* form yet another similar tree of related components.
*
* @author Clément Fournier
*/
public interface ApplicationComponent {
DesignerRoot getDesignerRoot();
/**
* A debug name for this component, used in developer mode to e.g. trace events
* handling paths.
*/
default String getDebugName() {
return getClass().getSimpleName();
}
/**
* Gets the logger of the application. Events pushed to the logger
* are filtered then forwarded to the Event Log control.
*
* @return The logger
*/
default EventLogger getLogger() {
return getDesignerRoot().getLogger();
}
/**
* Gets the main stage of the application.
*/
default Stage getMainStage() {
return getDesignerRoot().getMainStage();
}
/**
* If true, some more events are pushed to the event log, and
* console streams are open. This is enabled by the -v or --verbose
* option on command line for now.
*/
default boolean isDeveloperMode() {
return getDesignerRoot().isDeveloperMode();
}
/**
* Notify the logger of an exception that somewhere in PMD logic. Exceptions raised
* by the app logic are considered internal and should be forwarded to the logger
* using {@link #logInternalException(Throwable)}. If we're not in developer mode
* they will be ignored.
*/
default void logUserException(Throwable throwable, Category category) {
getLogger().logEvent(LogEntry.createUserExceptionEntry(throwable, category));
}
/**
* Notify the logger that XPath parsing succeeded and that the last recent failure may be thrown away.
* Only logged in developer mode.
*/
default void raiseParsableXPathFlag() {
getLogger().logEvent(LogEntry.createUserFlagEntry(Category.XPATH_OK));
}
/**
* Notify the logger that source code parsing succeeded and that the last recent failure may be thrown away.
* Only logged in developer mode.
*/
default void raiseParsableSourceFlag() {
getLogger().logEvent(LogEntry.createUserFlagEntry(Category.PARSE_OK));
}
// Internal log handlers
/** Logs an exception that occurred somewhere in the app logic. */
default void logInternalException(Throwable throwable) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createInternalExceptionEntry(throwable));
}
}
/** Logs an exception that occurred somewhere in the app logic. */
default void logInternalDebugInfo(Supplier<String> shortMessage, Supplier<String> details) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createInternalDebugEntry(shortMessage.get(), details.get()));
}
}
/** Logs a tracing event pushed by a {@link NodeSelectionSource}. */
default void logSelectionEventTrace(NodeSelectionEvent event, Supplier<String> details) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createNodeSelectionEventTraceEntry(event, details.get()));
}
}
}

View File

@ -2,7 +2,7 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
package net.sourceforge.pmd.util.fxdesigner.app;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
@ -13,17 +13,20 @@ import javafx.collections.ObservableSet;
/**
* A {@link NodeSelectionSource} that merges the events of several sub-components. Such a source
* can also handle events itself via {@link #setFocusNode(Node)}.
*
* @author Clément Fournier
*/
public interface CompositeSelectionSource extends NodeSelectionSource {
ObservableSet<? extends NodeSelectionSource> getComponents();
/** Returns the sources to forward to when bubbling down, and from which to merge events when bubbling up. */
ObservableSet<? extends NodeSelectionSource> getSubSelectionSources();
@Override
default EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.merge(getComponents(), NodeSelectionSource::getSelectionEvents);
return EventStreams.merge(getSubSelectionSources(), NodeSelectionSource::getSelectionEvents);
}
@ -35,12 +38,12 @@ public interface CompositeSelectionSource extends NodeSelectionSource {
@Override
default void select(NodeSelectionEvent selectionEvent) {
NodeSelectionSource.super.select(selectionEvent);
default void bubbleDown(NodeSelectionEvent selectionEvent) {
NodeSelectionSource.super.bubbleDown(selectionEvent);
for (NodeSelectionSource source : getComponents()) {
for (NodeSelectionSource source : getSubSelectionSources()) {
logSelectionEventTrace(selectionEvent, () -> getDebugName() + " forwards to " + source.getDebugName());
source.select(selectionEvent);
source.bubbleDown(selectionEvent);
}
}
}

View File

@ -2,9 +2,7 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner;
import net.sourceforge.pmd.util.fxdesigner.model.EventLogger;
package net.sourceforge.pmd.util.fxdesigner.app;
import javafx.stage.Stage;
@ -19,13 +17,14 @@ public final class DesignerRoot {
private final Stage mainStage;
private final EventLogger logger = new EventLogger(this);
private final EventLogger logger;
private final boolean developerMode;
public DesignerRoot(Stage mainStage, boolean developerMode) {
this.mainStage = mainStage;
this.developerMode = developerMode;
this.logger = new EventLogger(this);
}
@ -49,6 +48,11 @@ public final class DesignerRoot {
}
/**
* If true, some more events are pushed to the event log, and
* console streams are open. This is enabled by the -v or --verbose
* option on command line for now.
*/
public boolean isDeveloperMode() {
return developerMode;
}

View File

@ -2,13 +2,13 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.model;
package net.sourceforge.pmd.util.fxdesigner.app;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_EXCEPTION;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.PARSE_OK;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.SELECTION_EVENT_TRACING;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_EVALUATION_EXCEPTION;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.XPATH_OK;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.PARSE_EXCEPTION;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.PARSE_OK;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.SELECTION_EVENT_TRACING;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.XPATH_EVALUATION_EXCEPTION;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.XPATH_OK;
import static net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil.countNotMatching;
import static net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil.reduceEntangledIfPossible;
@ -23,12 +23,13 @@ import org.reactfx.collection.LiveArrayList;
import org.reactfx.collection.LiveList;
import org.reactfx.value.Val;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.LogEntryWithData;
import net.sourceforge.pmd.util.fxdesigner.util.ApplicationComponent;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.LogEntryWithData;
import net.sourceforge.pmd.util.fxdesigner.app.ApplicationComponent;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource.NodeSelectionEvent;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource.NodeSelectionEvent;
/**
@ -51,7 +52,7 @@ public class EventLogger implements ApplicationComponent {
public EventLogger(DesignerRoot designerRoot) {
this.designerRoot = designerRoot;
this.designerRoot = designerRoot; // we have to be careful with initialization order here
EventStream<LogEntryWithData<NodeSelectionEvent>> eventTraces =
reduceEntangledIfPossible(filterOnCategory(latestEvent, false, SELECTION_EVENT_TRACING).map(t -> (LogEntryWithData<NodeSelectionEvent>) t),

View File

@ -2,17 +2,16 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.model;
package net.sourceforge.pmd.util.fxdesigner.app;
import static net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category.CategoryType.FLAG;
import static net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category.CategoryType.FLAG;
import java.time.Instant;
import java.util.Date;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.reactfx.value.Var;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource.NodeSelectionEvent;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource.NodeSelectionEvent;
/**

View File

@ -2,7 +2,7 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
package net.sourceforge.pmd.util.fxdesigner.app;
import java.util.Objects;
@ -10,28 +10,42 @@ import org.reactfx.EventStream;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.MainDesignerController;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner;
import net.sourceforge.pmd.util.fxdesigner.util.controls.AstTreeView;
import net.sourceforge.pmd.util.fxdesigner.util.controls.NodeParentageCrumbBar;
/**
* A control or controller that has the ability to push node selection events.
* When a node is selected in the control (e.g. {@link AstTreeView}, {@link NodeParentageCrumbBar}, etc),
* the whole UI is synchronized to the node. Selection events are merged iteratively into
* a global stream for the whole app. Events from that stream are handled by {@link MainDesignerController}.
* A control or controller that somehow displays nodes in a form that the user can select.
* When a node is selected by the user (e.g. {@link AstTreeView}, {@link NodeParentageCrumbBar}, etc),
* the whole UI is synchronized to reflect information about the node. This includes scrolling
* the TreeView, the editor, etc. To achieve that uniformly, node selection events are merged
* into a global stream for the whole app. Events from that stream are handled by {@link MainDesignerController}.
*
* <p>Node selection sources form a tree parallel to {@link AbstractController} and {@link SettingsOwner}.
* This interface implements behaviour for leaves of the tree. Inner nodes are handled by
* {@link CompositeSelectionSource}.
*
* @author Clément Fournier
*/
public interface NodeSelectionSource extends ApplicationComponent {
/**
* Returns a stream of nodes that pushes an event every time
* this control records a *user* change in selection.
* Returns a stream of events that should push an event each time
* this source or one of its sub components records a change in node
* selection.
*/
EventStream<NodeSelectionEvent> getSelectionEvents();
default void select(NodeSelectionEvent selectionEvent) {
/**
* Bubbles a selection event down the tree. First, {@link #setFocusNode(Node)} is called to
* handle the event (if the event didn't originate from here). If this is not a leaf of the tree,
* then the event is forwarded to the children nodes as well.
*
* @param selectionEvent Event to handle
*/
default void bubbleDown(NodeSelectionEvent selectionEvent) {
if (alwaysHandleSelection() || selectionEvent.getOrigin() != this) {
logSelectionEventTrace(selectionEvent, () -> "\t" + this.getDebugName() + " is handling event");
setFocusNode(selectionEvent.getSelection());
@ -39,14 +53,24 @@ public interface NodeSelectionSource extends ApplicationComponent {
}
/**
* Updates the UI to react to a change in focus node. This is called whenever some selection source
* in the tree records a change. The event is not forwarded to its origin unless {@link #alwaysHandleSelection()}
* is overridden to return true.
*/
void setFocusNode(Node node);
/** Whether to also handle events which originated from this controller. */
default boolean alwaysHandleSelection() {
return false;
}
/**
* An event fired when the user selects a node somewhere in the UI
* and bubbled up to the {@link MainDesignerController}.
*/
final class NodeSelectionEvent {
private final Node selection;

View File

@ -0,0 +1,8 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* Interfaces and base classes that structure an support the whole app.
*/
package net.sourceforge.pmd.util.fxdesigner.app;

View File

@ -15,13 +15,14 @@ import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.LanguageVersionHandler;
import net.sourceforge.pmd.lang.Parser;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.util.ApplicationComponent;
import net.sourceforge.pmd.util.fxdesigner.SourceEditorController;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.ApplicationComponent;
/**
* Main class of the model. Manages a compilation unit.
* Manages a compilation unit for {@link SourceEditorController}.
*
* @author Clément Fournier
* @since 6.0.0

View File

@ -5,7 +5,6 @@
package net.sourceforge.pmd.util.fxdesigner.model;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import net.sourceforge.pmd.lang.metrics.MetricKey;
@ -23,11 +22,6 @@ public class MetricResult {
}
MetricResult(Entry<? extends MetricKey<?>, ? extends Double> entry) {
simpleEntry = new SimpleEntry<>(entry);
}
public MetricKey<?> getKey() {
return simpleEntry.getKey();
}

View File

@ -15,7 +15,6 @@ import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.rules.RuleBuilder;
import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec;
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.beans.SettingsPersistenceUtil.PersistentSequence;

View File

@ -2,7 +2,7 @@
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
package net.sourceforge.pmd.util.fxdesigner.model;
import java.util.HashMap;

View File

@ -19,7 +19,6 @@ 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.util.fxdesigner.util.PropertyDescriptorSpec;
/**

View File

@ -0,0 +1,10 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* PMD's rule designer. A good starting point to learn about the global structure of the app
* is {@link net.sourceforge.pmd.util.fxdesigner.app.ApplicationComponent}, and
* {@link net.sourceforge.pmd.util.fxdesigner.app.AbstractController}.
*/
package net.sourceforge.pmd.util.fxdesigner;

View File

@ -15,7 +15,7 @@ import java.util.function.Consumer;
import org.reactfx.collection.LiveList;
import org.reactfx.value.Val;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import javafx.beans.binding.Bindings;

View File

@ -22,7 +22,7 @@ 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.model.PropertyDescriptorSpec;
import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView;
import javafx.application.Platform;

View File

@ -19,11 +19,11 @@ import org.reactfx.value.Val;
import org.reactfx.value.Var;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.MainDesignerController;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.app.EventLogger;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry;
import net.sourceforge.pmd.util.fxdesigner.app.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.app.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import javafx.beans.property.SimpleObjectProperty;
@ -47,20 +47,17 @@ import javafx.stage.Stage;
/**
* A presenter over the {@link net.sourceforge.pmd.util.fxdesigner.model.EventLogger}.
* A presenter over the {@link EventLogger}.
* There's not necessarily one in the app, it can be garbage collected and recreated.
* Each of these necessarily has a live UI component though.
*
* @author Clément Fournier
* @since 6.0.0
*/
public final class EventLogController extends AbstractController {
public final class EventLogController extends AbstractController<MainDesignerController> {
private static final PseudoClass NEW_ENTRY = PseudoClass.getPseudoClass("new-entry");
private final DesignerRoot designerRoot;
private final MainDesignerController mediator;
@FXML
private TableView<LogEntry> eventLogTableView;
@FXML
@ -82,18 +79,12 @@ public final class EventLogController extends AbstractController {
public EventLogController(MainDesignerController mediator) {
this.designerRoot = mediator.getDesignerRoot();
this.mediator = mediator;
super(mediator);
// the FXML fields are injected and initialize is called in createStage
this.myPopupStage = createStage(designerRoot.getMainStage());
this.myPopupStage = createStage(getMainStage());
}
@Override
public DesignerRoot getDesignerRoot() {
return designerRoot;
}
// this is only called each time a popup is created
@Override
@ -193,18 +184,18 @@ public final class EventLogController extends AbstractController {
.successionEnds(Duration.ofMillis(100))
.subscribe(b -> {
if (b) {
mediator.handleSelectedNodeInError(selectedErrorNodes.getValue());
parent.handleSelectedNodeInError(selectedErrorNodes.getValue());
} else {
mediator.resetSelectedErrorNodes();
parent.resetSelectedErrorNodes();
}
})
);
binding = binding.and(
selectedErrorNodes.values().subscribe(mediator::handleSelectedNodeInError)
selectedErrorNodes.values().subscribe(parent::handleSelectedNodeInError)
);
SortedList<LogEntry> logEntries = new SortedList<>(designerRoot.getLogger().getLog(), Comparator.reverseOrder());
SortedList<LogEntry> logEntries = new SortedList<>(getLogger().getLog(), Comparator.reverseOrder());
eventLogTableView.itemsProperty().setValue(logEntries);
binding = binding.and(
() -> eventLogTableView.itemsProperty().setValue(FXCollections.emptyObservableList())
@ -228,7 +219,7 @@ public final class EventLogController extends AbstractController {
entry.setExamined(true);
if (entry.getCategory().isUserException()) {
DesignerUtil.stackTraceToXPath(entry.getDetails()).map(mediator::runXPathQuery).ifPresent(selectedErrorNodes::setValue);
DesignerUtil.stackTraceToXPath(entry.getDetails()).map(parent::runXPathQuery).ifPresent(selectedErrorNodes::setValue);
}
}
@ -254,7 +245,7 @@ public final class EventLogController extends AbstractController {
private Val<String> titleProperty() {
return designerRoot.getLogger().numNewLogEntriesProperty().map(i -> "Exception log (" + (i > 0 ? i : "no") + " new)");
return parent.getLogger().numNewLogEntriesProperty().map(i -> "Exception log (" + (i > 0 ? i : "no") + " new)");
}

View File

@ -1,79 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
import java.util.function.Supplier;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.model.EventLogger;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry;
import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource.NodeSelectionEvent;
/**
* Some part of the application, linked to the {@link DesignerRoot}.
*
* @author Clément Fournier
*/
public interface ApplicationComponent {
DesignerRoot getDesignerRoot();
default String getDebugName() {
return getClass().getSimpleName();
}
default EventLogger getLogger() {
return getDesignerRoot().getLogger();
}
default boolean isDeveloperMode() {
return getDesignerRoot().isDeveloperMode();
}
default void logUserException(Throwable throwable, Category category) {
getLogger().logEvent(LogEntry.createUserExceptionEntry(throwable, category));
}
default void raiseParsableXPathFlag() {
getLogger().logEvent(LogEntry.createUserFlagEntry(Category.XPATH_OK));
}
default void raiseParsableSourceFlag() {
getLogger().logEvent(LogEntry.createUserFlagEntry(Category.PARSE_OK));
}
// Internal log handlers
default void logInternalException(Throwable throwable) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createInternalExceptionEntry(throwable));
}
}
default void logInternalDebugInfo(Supplier<String> shortMessage, Supplier<String> details) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createInternalDebugEntry(shortMessage.get(), details.get()));
}
}
default void logSelectionEventTrace(NodeSelectionEvent event, Supplier<String> details) {
if (isDeveloperMode()) {
getLogger().logEvent(LogEntry.createNodeSelectionEventTraceEntry(event, details.get()));
}
}
}

View File

@ -18,8 +18,8 @@ import org.reactfx.SuspendableEventStream;
import org.reactfx.value.Var;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TreeItem;

View File

@ -17,8 +17,8 @@ import org.reactfx.EventStream;
import org.reactfx.value.Val;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.app.DesignerRoot;
import net.sourceforge.pmd.util.fxdesigner.app.NodeSelectionSource;
import javafx.application.Platform;
import javafx.css.PseudoClass;

View File

@ -13,7 +13,7 @@ import org.reactfx.value.Var;
import net.sourceforge.pmd.properties.PropertyTypeId;
import net.sourceforge.pmd.util.fxdesigner.popups.EditPropertyDialogController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec;
import net.sourceforge.pmd.util.fxdesigner.model.PropertyDescriptorSpec;
import net.sourceforge.pmd.util.fxdesigner.util.SoftReferenceCache;
import javafx.application.Platform;