Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] XPath AutoComplete #1182

Merged
merged 34 commits into from Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7a6e40c
Start on the autocomplete feature made basic UI upgrade to show the p…
akshatbahety Jun 8, 2018
eddb9be
Xpath suggestions , under progress
akshatbahety Jun 9, 2018
6a88b5d
The basic working is there the suggestions are being filtered on the …
akshatbahety Jun 9, 2018
0a85e86
Made the changes as required , some pending
akshatbahety Jun 10, 2018
2aaa468
Minor Updates
akshatbahety Jun 10, 2018
67da35b
Minor Fixes
akshatbahety Jun 10, 2018
7f99c20
Git issue
akshatbahety Jun 10, 2018
9770c88
Got the String matching sorted
akshatbahety Jun 10, 2018
44c464c
Minor Update
akshatbahety Jun 10, 2018
48c4e2a
Basic functioning of autocomplete is ready
akshatbahety Jun 10, 2018
2af2aaf
Minor Changes
akshatbahety Jun 10, 2018
51e0180
Minor Fixes
akshatbahety Jun 11, 2018
cbf8e6a
PR fixes
akshatbahety Jun 12, 2018
a7113b7
Some PR Fixes as suggested , still working on others
akshatbahety Jun 12, 2018
a5c8308
Minor Fixes
akshatbahety Jun 12, 2018
05a733d
String Filtering , I think this is working
akshatbahety Jun 12, 2018
a3d8a80
Minor Fixes , Fixed the AutoCorrect String Bug
akshatbahety Jun 12, 2018
0447fd3
String Filter and Replace Update
akshatbahety Jun 12, 2018
3ec5136
Highlight the suggestions , some bugs fixing them
akshatbahety Jun 13, 2018
da24020
Minor fixes
akshatbahety Jun 13, 2018
23d0005
Minor fixes
akshatbahety Jun 13, 2018
ee2678a
Minor fixes , working on others
akshatbahety Jun 13, 2018
1e839a6
Display Autocomplete near carat , it's a hack
akshatbahety Jun 13, 2018
4bb34b1
Minor Update
akshatbahety Jun 14, 2018
32136e0
Bug Fixes
akshatbahety Jun 14, 2018
2db426f
Minor Removal Fix
akshatbahety Jun 14, 2018
7e07208
Minor fixes
akshatbahety Jun 14, 2018
39ec32e
Minor Fix
akshatbahety Jun 15, 2018
978d77a
Minor Fix
akshatbahety Jun 20, 2018
9c71a70
Minor Fix
akshatbahety Jun 20, 2018
edeefa7
Minor Fix
akshatbahety Jun 25, 2018
118ee1d
Merge branch 'master' into pr-1182
oowekyala Jun 27, 2018
4b8dba8
Fix startup bug
oowekyala Jun 27, 2018
7f3c93d
PMD DogFood
akshatbahety Jun 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7,6 +7,7 @@
import java.io.IOException;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand All @@ -28,6 +29,7 @@
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.model.XPathSuggestions;
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;
Expand All @@ -50,6 +52,8 @@
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.control.TitledPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.stage.Modality;
Expand Down Expand Up @@ -84,6 +88,8 @@ public class XPathPanelController implements Initializable, SettingsOwner {
@SuppressWarnings("PMD.SingularField")
private ChoiceBox<String> xpathVersionChoiceBox;

private ContextMenu autoCompletePopup;


public XPathPanelController(DesignerRoot owner, MainDesignerController mainController) {
this.designerRoot = owner;
Expand Down Expand Up @@ -112,9 +118,66 @@ public void initialize(URL location, ResourceBundle resources) {
// Reevaluate XPath anytime the expression or the XPath version changes
.or(xpathVersionProperty().changes())
.subscribe(tick -> parent.refreshXPathResults());

xpathExpressionArea.plainTextChanges()
.filter(t -> (t.getInserted().matches("[a-zA-Z]")) || t.getInserted().matches("/"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these 2 checks could be merged together in matches("[a-zA-Z/]")

.subscribe(t -> {
try {
if (t.getInserted().equals("/")) {
autoComplete(xpathExpressionArea.getText().substring(xpathExpressionArea.getText().indexOf("/")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we interested only in the first slash of the expression?

} else if (!t.getInserted().equals("/")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if A
else if not A ?

This can be reduced to a simple if / else

autoComplete(xpathExpressionArea.getText().substring(xpathExpressionArea.getText().lastIndexOf("/")));
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to work these exceptions out eventually

}
});
}

private void autoComplete(String s) throws IOException, ClassNotFoundException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give a more descriptive name to the parameter (eg input or whatever but s)


autoCompletePopup = new ContextMenu();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still create a new context menu each time!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make the update, sorry for the confusion before

List<MenuItem> resultToDisplay = new ArrayList<>();

String language = parent.getLanguageVersion().getName().replaceAll("[0-9]", "").replaceAll("//s", "").toLowerCase().trim();
XPathSuggestions xPathSuggestions = new XPathSuggestions("net.sourceforge.pmd.lang." + language + ".ast");
Copy link
Member

@jsotuyod jsotuyod Jun 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that XPathSuggestions knows about this package (it filters out on XPathSuggestions .evaluateXpathSuggestions, I'd consider just having the constructor receive the language name, and have the rest of the logic moved there. This way, there is a single class that needs to care about what packages are expected to contain AST nodes for a given language.


List<String> suggestions = xPathSuggestions.getXPathSuggestions();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestions depend on the input string entered by the user. Add a parameter to your method which contains that and retrieve it from the change stream

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XPathSuggestions should encapsulate the algorithm to find suggestions from the input string. The call should look like xpathSuggestions.getSuggestions(input) and return only the suggestions that match the string, the definiton of "x matches s" being defined by that procedure.

Then you could for example stream the list, map it to menu items, and take only the first 5 to not overwhelm the user.


for (String s1 : suggestions) {
if (s1.contains(s.replace("/", "").trim())) {
MenuItem m = new MenuItem(s1);
if (!resultToDisplay.contains(m)) {
resultToDisplay.add(m);
}
}
}

//TODO: Work on the implementation of the Result to be selected and added to the Code Area
autoCompletePopup.getItems().addAll(resultToDisplay);

if (xpathExpressionArea.getText().length() > 0) {

xpathExpressionArea.addEventHandler(KeyEvent.KEY_TYPED, t -> {
if (t.getCode() != KeyCode.ESCAPE) {
xpathExpressionArea.setContextMenu(autoCompletePopup);
}
});

xpathExpressionArea.addEventHandler(KeyEvent.KEY_PRESSED, t -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the handler should probably belong to the popup

if (t.getCode() == KeyCode.ESCAPE) {
autoCompletePopup.hide();
}
});


}
}



private void initGenerateXPathFromStackTrace() {

ContextMenu menu = new ContextMenu();
Expand Down
@@ -0,0 +1,97 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/

package net.sourceforge.pmd.util.fxdesigner.model;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;


public class XPathSuggestions {
private List<String> xPathSuggestions = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between listOfSuggestions and this field?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the field final and name it better

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the field final. And a better name would be "availableNodeNames" or something like that

String packagename;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is intented to be package-private, document it. If it's not, please change it to proper visibility (private?)


public XPathSuggestions(String packageName) {
this.packagename = packageName;
}


public List<String> getXPathSuggestions() throws IOException, ClassNotFoundException {
return evaluateXpathSuggestions(createList(getClasses(packagename)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you reevaluate everything each time. Keep in mind your list of all node names only be evaluated once, eg in the constructor. Also, throwing checked exceptions here seems really sloppy. The exceptions should be taken care of when the list is created in the constructor, and should be recovered from in this class. Clients of this class have no idea how to recover from them

}

private List<String> createList(Class[] classArray) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use more descriptive names and add documentation comments.

List<Class> fileNameList = Arrays.asList(classArray);
List<String> foo = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"foo"?


for (Class c : fileNameList) {

if (c.getName().contains("AST")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstracting that into a function (isAstNode(Class)) would be better. The filtering misses some things too, for example, it should filter out classes whose names contain a $ symbol (nested classes), and classes that are not concrete.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.getSimpleName().startsWith("AST")

foo.add(c.getName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're looking for c.getSimpleName.substring("AST".length())

}
}

return foo;
}

private static Class[] getClasses(String packageName) throws ClassNotFoundException, IOException {

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;

String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);

List<File> dirs = new ArrayList<>();

while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}

ArrayList<Class> classes = new ArrayList<>();

for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}

return classes.toArray(new Class[classes.size()]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to 0-size arrays when doing toArray(). PMD will enforce this.

}


private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {

List<Class> classes = new ArrayList<>();
if (!directory.exists()) {
return classes;
}

File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to recurse here for our use case

assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + "." + file.getName().substring(0, file.getName().length() - 6)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this 6 seems rather magical... ".class".length() I assume?

}
}

return classes;
}

private List<String> evaluateXpathSuggestions(List<String> fileNameList) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the point of having a separate method, just merge that into the createList

for (String s : fileNameList) {
//Check if the package name should be hardcoded
xPathSuggestions.add(s.replace("AST", "").replace(".java", "").replace("net.sourceforge.pmd.lang.ast" + ".", ""));
}
return xPathSuggestions;
}


}