diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java index d9adf73bc5f..841baa81e5f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java @@ -33,11 +33,25 @@ public class ClasspathClassLoader extends URLClassLoader { static { registerAsParallelCapable(); } - + + public ClasspathClassLoader(List files, ClassLoader parent) throws IOException { + super(fileToURL(files), parent); + } + public ClasspathClassLoader(String classpath, ClassLoader parent) throws IOException { super(initURLs(classpath), parent); } + private static URL[] fileToURL(List files) throws IOException { + + List urlList = new ArrayList<>(); + + for (File f : files) { + urlList.add(f.toURI().toURL()); + } + return urlList.toArray(new URL[0]); + } + private static URL[] initURLs(String classpath) throws IOException { if (classpath == null) { throw new IllegalArgumentException("classpath argument cannot be null"); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/AuxClassPathController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/AuxClassPathController.java new file mode 100644 index 00000000000..059dc3b21a5 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/AuxClassPathController.java @@ -0,0 +1,143 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.util.fxdesigner; + +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +import org.reactfx.value.Var; + +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; + +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.stage.FileChooser; + +public class AuxClassPathController implements Initializable, SettingsOwner { + + private final DesignerRoot designerRoot; + + private final Var onCancel = Var.newSimpleVar(() -> {}); + private final Var>> onApply = Var.newSimpleVar(l -> {}); + + + @FXML + private Button removeFileButton; + @FXML + private Button selectFilesButton; + @FXML + private ListView fileListView = new ListView<>(); + @FXML + private Button moveItemUpButton; + @FXML + private Button moveItemDownButton; + @FXML + private Button setClassPathButton; + @FXML + private Button cancelButton; + + + public AuxClassPathController(DesignerRoot designerRoot) { + this.designerRoot = designerRoot; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + + + removeFileButton.disableProperty().bind(fileListView.getSelectionModel().selectedItemProperty().isNull()); + moveItemUpButton.disableProperty().bind(fileListView.getSelectionModel().selectedItemProperty().isNull()); + moveItemDownButton.disableProperty().bind(fileListView.getSelectionModel().selectedItemProperty().isNull()); + + + + selectFilesButton.setOnAction(e -> onSelectFileClicked()); + removeFileButton.setOnAction(e -> onRemoveFileClicked()); + setClassPathButton.setOnAction(e -> onApply.ifPresent(f -> f.accept(fileListView.getItems()))); + moveItemUpButton.setOnAction(e -> moveUp()); + moveItemDownButton.setOnAction(e -> moveDown()); + cancelButton.setOnAction(e -> onCancel.ifPresent(Runnable::run)); + + } + + + private void onSelectFileClicked() { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Select Files"); + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Java JARs", "*.jar"), + new FileChooser.ExtensionFilter("Java WARs", "*.war"), + new FileChooser.ExtensionFilter("Java EARs", "*.ear"), + new FileChooser.ExtensionFilter("Java class files", "*.class") + ); + List files = chooser.showOpenMultipleDialog(designerRoot.getMainStage()); + fileListView.getItems().addAll(files); + } + + + private void onRemoveFileClicked() { + File f = fileListView.getSelectionModel().getSelectedItem(); + fileListView.getItems().remove(f); + } + + + public void setAuxclasspathFiles(List lst) { + fileListView.setItems(FXCollections.observableArrayList(lst)); + } + + + public void setOnCancel(Runnable run) { + onCancel.setValue(run); + } + + + public void setOnApply(Consumer> onApply) { + this.onApply.setValue(onApply); + } + + + private void moveUp() { + moveItem(-1); + } + + + private void moveDown() { + moveItem(1); + } + + + public void moveItem(int direction) { + // Checking selected item + if (fileListView.getSelectionModel().getSelectedItem() == null) { + return; + } + + // Calculate new index using move direction + int newIndex = fileListView.getSelectionModel().getSelectedIndex() + direction; + + if (newIndex < 0 || newIndex >= fileListView.getItems().size()) { + return; + } + + File selected = fileListView.getSelectionModel().getSelectedItem(); + + // Removing removable element + fileListView.getItems().remove(selected); + // Insert it in new position + fileListView.getItems().add(newIndex, selected); + //Restore Selection + fileListView.scrollTo(newIndex); + fileListView.getSelectionModel().select(newIndex); + + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java index 3eb6133f0a0..8d5e4a14cf2 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java @@ -73,8 +73,11 @@ public class MainDesignerController implements Initializable, SettingsOwner { */ private final DesignerRoot designerRoot; + /* Menu bar */ @FXML + private MenuItem setupAuxclasspathMenuItem; + @FXML private MenuItem openFileMenuItem; @FXML private MenuItem licenseMenuItem; @@ -161,6 +164,16 @@ public void initialize(URL location, ResourceBundle resources) { } }); + setupAuxclasspathMenuItem.setOnAction(e -> { + try { + sourceEditorController.showAuxClassPathController(designerRoot); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + + + sourceEditorController.refreshAST(); xpathPanelController.evaluateXPath(sourceEditorController.getCompilationUnit(), getLanguageVersion()); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java index 891ac4eb874..86c434c4488 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java @@ -4,14 +4,19 @@ package net.sourceforge.pmd.util.fxdesigner; +import java.io.File; +import java.io.IOException; import java.net.URL; import java.time.Duration; +import java.util.Arrays; 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 java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.fxmisc.richtext.LineNumberFactory; @@ -22,8 +27,10 @@ import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.ast.Node; +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.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.AvailableSyntaxHighlighters; @@ -34,11 +41,16 @@ import net.sourceforge.pmd.util.fxdesigner.util.controls.TreeViewWrapper; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.SelectionModel; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; +import javafx.stage.Modality; +import javafx.stage.Stage; /** @@ -50,6 +62,7 @@ public class SourceEditorController implements Initializable, SettingsOwner { private final MainDesignerController parent; + @FXML private Label astTitleLabel; @FXML @@ -62,14 +75,22 @@ public class SourceEditorController implements Initializable, SettingsOwner { private ASTTreeItem selectedTreeItem; private static final Duration AST_REFRESH_DELAY = Duration.ofMillis(100); + private Var> auxclasspathFiles = Var.newSimpleVar(Collections.emptyList()); + private final Val auxclasspathClassLoader = auxclasspathFiles.map(fileList -> { + try { + return new ClasspathClassLoader(fileList, SourceEditorController.class.getClassLoader()); + } catch (IOException e) { + e.printStackTrace(); + } + return SourceEditorController.class.getClassLoader(); + }); + public SourceEditorController(DesignerRoot owner, MainDesignerController mainController) { parent = mainController; astManager = new ASTManager(owner); } - - @Override public void initialize(URL location, ResourceBundle resources) { @@ -88,7 +109,8 @@ public void initialize(URL location, ResourceBundle resources) { codeEditorArea.richChanges() .filter(t -> !t.isIdentity()) .successionEnds(AST_REFRESH_DELAY) - // Refresh the AST anytime the text or the language version changes + // Refresh the AST anytime the text, classloader, or language version changes + .or(auxclasspathClassLoader.changes()) .or(languageVersionProperty().changes()) .subscribe(tick -> { // Discard the AST if the language version has changed @@ -114,7 +136,7 @@ public void refreshAST() { } try { - current = astManager.updateCompilationUnit(source); + current = astManager.updateCompilationUnit(source, auxclasspathClassLoader.getValue()); } catch (ParseAbortedException e) { invalidateAST(true); return; @@ -126,6 +148,57 @@ public void refreshAST() { } + public void showAuxClassPathController(DesignerRoot root) { + AuxClassPathController auxClassPathController = new AuxClassPathController(root); + + FXMLLoader fxmlLoader = new FXMLLoader(DesignerUtil.getFxml("auxclasspath-setup-popup.fxml")); + + fxmlLoader.setControllerFactory(type -> { + if (type == AuxClassPathController.class) { + return auxClassPathController; + } else { + throw new IllegalStateException("Wrong controller!"); + } + }); + try { + Parent root1 = fxmlLoader.load(); + + auxClassPathController.setAuxclasspathFiles(auxclasspathFiles.getValue()); + + Stage stage = new Stage(); + stage.initOwner(root.getMainStage()); + stage.initModality(Modality.WINDOW_MODAL); + stage.setScene(new Scene(root1)); + stage.show(); + + auxClassPathController.setOnApply(files -> { + stage.close(); + auxclasspathFiles.setValue(files); + }); + + auxClassPathController.setOnCancel(stage::close); + + + } catch (IOException e) { + e.printStackTrace(); + } + + + } + + + @PersistentProperty + public String getAuxclasspathFiles() { + return auxclasspathFiles.getValue().stream().map(p -> p.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator)); + } + + + public void setAuxclasspathFiles(String files) { + List newVal = Arrays.asList(files.split(File.pathSeparator)).stream().map(File::new).collect(Collectors.toList()); + auxclasspathFiles.setValue(newVal); + } + + private void setUpToDateCompilationUnit(Node node) { astTitleLabel.setText("Abstract Syntax Tree"); ASTTreeItem root = ASTTreeItem.getRoot(node); @@ -182,7 +255,7 @@ public void focusNodeInTreeView(Node node) { // node is different from the old one if (selectedTreeItem == null && node != null - || selectedTreeItem != null && !Objects.equals(node, selectedTreeItem.getValue())) { + || selectedTreeItem != null && !Objects.equals(node, selectedTreeItem.getValue())) { ASTTreeItem found = ((ASTTreeItem) astTreeView.getRoot()).findItem(node); if (found != null) { selectionModel.select(found); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java index bb397e9cd39..f9959b4062d 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java @@ -226,7 +226,6 @@ public void invalidateResults(boolean error) { public void showExportXPathToRuleWizard() throws IOException { - // doesn't work for some reason ExportXPathWizardController wizard = new ExportXPathWizardController(xpathExpressionProperty()); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java index e9c7533e33f..f34fa372562 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java @@ -38,7 +38,8 @@ public class ASTManager { */ private LanguageVersion lastLanguageVersion; /** - * Latest computed compilation unit (only null before the first call to {@link #updateCompilationUnit(String)}) + * Latest computed compilation unit (only null before the first call to + * {@link #updateCompilationUnit(String, ClassLoader)}) */ private Var compilationUnit = Var.newSimpleVar(null); /** @@ -84,7 +85,7 @@ public Val compilationUnitProperty() { * * @throws ParseAbortedException if parsing fails and cannot recover */ - public Node updateCompilationUnit(String source) throws ParseAbortedException { + public Node updateCompilationUnit(String source, ClassLoader classLoader) throws ParseAbortedException { if (compilationUnit.isPresent() && getLanguageVersion().equals(lastLanguageVersion) && StringUtils.equals(source, lastValidSource)) { @@ -106,15 +107,13 @@ && getLanguageVersion().equals(lastLanguageVersion) designerRoot.getLogger().logEvent(new LogEntry(e, Category.SYMBOL_FACADE_EXCEPTION)); } try { - // TODO this should use the aux classpath - languageVersionHandler.getQualifiedNameResolutionFacade(ASTManager.class.getClassLoader()).start(node); + languageVersionHandler.getQualifiedNameResolutionFacade(classLoader).start(node); } catch (Exception e) { designerRoot.getLogger().logEvent(new LogEntry(e, Category.QUALIFIED_NAME_RESOLUTION_EXCEPTION)); } try { - // TODO this should use the aux classpath - languageVersionHandler.getTypeResolutionFacade(ASTManager.class.getClassLoader()).start(node); + languageVersionHandler.getTypeResolutionFacade(classLoader).start(node); } catch (Exception e) { designerRoot.getLogger().logEvent(new LogEntry(e, Category.TYPERESOLUTION_EXCEPTION)); } diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml new file mode 100644 index 00000000000..f66299513c2 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml index 190821848c1..4afb18add8f 100644 --- a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml @@ -18,14 +18,16 @@ - - + - + @@ -42,6 +44,13 @@ + + + + + +