Skip to content
Snippets Groups Projects
Commit a6536da4 authored by Tobias Ask's avatar Tobias Ask
Browse files

Update glossary so it reacts to changes in the workspace. Use AST analysis on...

Update glossary so it reacts to changes in the workspace. Use AST analysis on all changes, not just updates. On adds and removals, the AST needs to be constructed, but it is a small price to pay for an approach that handles all cases.

The glossary now needs the info panel controller reference in its constructor so it can notify the info panel about any changes. It also needs the file URL in case it is notified about a change in the workspace prior to the first query.
parent 6e534183
No related branches found
No related tags found
1 merge request!8Resolve "Update controller name suggestions on changes in the workspace"
Pipeline #
......@@ -468,7 +468,7 @@ public class InfoPanelController extends AbstractFxmlPanelController {
private final ChangeListener<Boolean> checkBoxListener = (ov, t, t1) -> toggleFxRoot();
private void resetSuggestedControllerClasses(URL location) {
public void resetSuggestedControllerClasses(URL location) {
if (controllerClassEditor != null) {
// The listener on fxmlLocationProperty is called before the file
// denoted by the location is created on disk, hence the runLater.
......
package no.tobask.sb4e.examples;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
@FXML
Label lbl;
}
......@@ -19,6 +19,7 @@ Import-Package: com.oracle.javafx.scenebuilder.app.selectionbar,
com.oracle.javafx.scenebuilder.kit.fxom,
org.eclipse.core.resources,
org.eclipse.jdt.core,
org.eclipse.jdt.core.dom,
org.eclipse.jdt.launching
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
......
package no.tobask.sb4e;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import javafx.fxml.FXML;
public class ControllerCandidateChecker extends ASTVisitor {
private static final String FXML_ANNOTATION = FXML.class.getSimpleName();
private boolean hasEmptyConstructor;
private boolean hasFxmlAnnotatedMembers;
private boolean hasConstructor;
@Override
public boolean visit(FieldDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(MethodDeclaration node) {
if (node.isConstructor()) {
hasConstructor = true;
hasEmptyConstructor = hasEmptyConstructor || node.parameters().size() == 0;
}
return visitBodyDeclaration(node);
}
@SuppressWarnings("unchecked")
private boolean visitBodyDeclaration(BodyDeclaration node) {
List<IExtendedModifier> modifiers = node.modifiers();
for (IExtendedModifier modifier : modifiers) {
if (modifier.isAnnotation()) {
String annotationName = ((Annotation) modifier).getTypeName().toString();
if (annotationName != null && annotationName.equals(FXML_ANNOTATION)) {
hasFxmlAnnotatedMembers = true;
}
}
}
return false;
}
public boolean visitedCandidate() {
return (hasEmptyConstructor || !hasConstructor) && hasFxmlAnnotatedMembers;
}
}
......@@ -3,14 +3,10 @@ package no.tobask.sb4e;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
......@@ -25,7 +21,10 @@ import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import com.oracle.javafx.scenebuilder.app.info.InfoPanelController;
import com.oracle.javafx.scenebuilder.kit.glossary.Glossary;
import javafx.beans.value.ObservableValue;
......@@ -34,25 +33,35 @@ import javafx.fxml.FXML;
public class JavaProjectGlossary extends Glossary implements IElementChangedListener {
private static final String FXML_ANNOTATION = FXML.class.getSimpleName();
private String controllerClassName;
private URL fxmlLocation;
private InfoPanelController infoPanelController;
private Map<String, List<String>> fxIds;
private List<String> eventHandlers;
private static final String FXML_ANNOTATION = FXML.class.getSimpleName();
private List<ICompilationUnit> candidateControllers;
public JavaProjectGlossary(String controllerClassName) {
public JavaProjectGlossary(String controllerClassName, URL fxmlLocation,
InfoPanelController infoPanelController) {
this.controllerClassName = controllerClassName;
JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE);
this.fxmlLocation = fxmlLocation;
this.infoPanelController = infoPanelController;
JavaCore.addElementChangedListener(this);
}
@Override
public List<String> queryControllerClasses(URL fxmlLocation) {
if (candidateControllers == null) {
IJavaProject project = JavaModelUtils.getJavaProjectFromUrl(fxmlLocation);
String packageName = JavaModelUtils.getPackageContainingFile(fxmlLocation).getElementName();
List<ICompilationUnit> candidates = new ArrayList<>();
candidateControllers = new ArrayList<>();
for (IPackageFragment pkg : JavaModelUtils.getAllMatchingPackages(packageName, project)) {
candidates.addAll(getCandidateControllers(pkg, fxmlLocation));
candidateControllers.addAll(getCandidateControllers(pkg));
}
}
return candidates.stream().map(c -> c.getElementName()).collect(Collectors.toList());
return candidateControllers.stream().map(c -> c.getElementName()).collect(Collectors.toList());
}
@Override
......@@ -82,60 +91,81 @@ public class JavaProjectGlossary extends Glossary implements IElementChangedList
@Override
public void elementChanged(ElementChangedEvent event) {
if (controllerClassName != null) {
ICompilationUnit controllerClass = getControllerClass(event);
if (controllerClass != null) {
fxIds = getFxIds(controllerClass);
eventHandlers = getEventHandlers(controllerClass);
IJavaElementDelta delta = getClassChangedDelta(event.getDelta());
if (delta != null) {
ICompilationUnit affectedClass = (ICompilationUnit) delta.getElement();
if (fxmlLocation != null && inSameProject(affectedClass)) {
if (updateControllerCandidates(delta)) {
infoPanelController.resetSuggestedControllerClasses(fxmlLocation);
}
if (controllerClassName != null && affectedClass.getElementName()
.equals(controllerClassName) && delta.getKind() != IJavaElementDelta.REMOVED) {
fxIds = getFxIds(affectedClass);
eventHandlers = getEventHandlers(affectedClass);
}
}
}
}
private Collection<ICompilationUnit> getCandidateControllers(IPackageFragment pkg,
URL fxmlLocation) {
try {
List<ICompilationUnit> allUnits = new ArrayList<>(Arrays.asList(pkg.
getCompilationUnits()));
Stream<ICompilationUnit> matches = allUnits.stream().filter(u
-> isCandidateControllerClass(u, fxmlLocation));
return matches.collect(Collectors.toList());
} catch (JavaModelException e) {
e.printStackTrace();
return new ArrayList<>();
private boolean updateControllerCandidates(IJavaElementDelta delta) {
boolean updated = false;
ICompilationUnit compUnit = (ICompilationUnit) delta.getElement();
if (delta.getKind() == IJavaElementDelta.REMOVED) {
if (candidateControllers.contains(compUnit)) {
candidateControllers.remove(compUnit);
updated = true;
}
} else {
CompilationUnit ast = delta.getCompilationUnitAST();
CompilationUnit clazz = ast != null ? ast : getAst(compUnit);
ControllerCandidateChecker checker = new ControllerCandidateChecker();
clazz.accept(checker);
boolean isCandidateController = checker.visitedCandidate();
if (candidateControllers.contains(compUnit)) {
if (!isCandidateController) {
candidateControllers.remove(compUnit);
updated = true;
}
} else if (isCandidateController) {
candidateControllers.add(compUnit);
updated = true;
}
}
return updated;
}
private boolean isCandidateControllerClass(ICompilationUnit javaClass, URL fxmlLocation) {
IType type = javaClass.findPrimaryType();
return hasNoOrEmptyConstructor(type) && hasFxmlAnnotatedMembers(type);
private CompilationUnit getAst(ICompilationUnit source) {
ASTParser parser = ASTParser.newParser(AST.JLS9);
parser.setSource(source);
return (CompilationUnit) parser.createAST(null);
}
private boolean hasFxmlAnnotatedMembers(IType type) {
try {
List<IMethod> methods = new ArrayList<>(Arrays.asList(type.getMethods()));
List<IField> fields = new ArrayList<>(Arrays.asList(type.getFields()));
boolean anyMethodsAnnotated = methods.stream().anyMatch(m ->
m.getAnnotation(FXML_ANNOTATION).exists());
boolean anyFieldsAnnotated = fields.stream().anyMatch(f ->
f.getAnnotation(FXML_ANNOTATION).exists());
return anyFieldsAnnotated || anyMethodsAnnotated;
} catch (JavaModelException e) {
e.printStackTrace();
return false;
private IJavaElementDelta getClassChangedDelta(IJavaElementDelta rootDelta) {
IJavaElementDelta delta = rootDelta;
IJavaElement element = delta.getElement();
while (delta.getAffectedChildren().length > 0 &&
!(element.getElementType() == IJavaElement.COMPILATION_UNIT)) {
delta = delta.getAffectedChildren()[0];
element = delta.getElement();
}
return element.getElementType() == IJavaElement.COMPILATION_UNIT ? delta : null;
}
private boolean hasNoOrEmptyConstructor(IType type) {
try {
for (IMethod method : type.getMethods()) {
if (method.isConstructor()) {
return method.getParameters().length == 0;
}
private boolean inSameProject(ICompilationUnit clazz) {
return clazz.getJavaProject().equals(JavaModelUtils.getJavaProjectFromUrl(fxmlLocation));
}
private List<ICompilationUnit> getCandidateControllers(IPackageFragment pkg) {
ASTParser parser = ASTParser.newParser(AST.JLS9);
Requestor requestor = new Requestor();
try {
parser.createASTs(pkg.getCompilationUnits(), null, requestor, null);
return requestor.getCandidates();
} catch (JavaModelException e) {
e.printStackTrace();
return new ArrayList<>();
}
return true;
}
private List<String> getEventHandlers(ICompilationUnit controller) {
......@@ -206,22 +236,4 @@ public class JavaProjectGlossary extends Glossary implements IElementChangedList
}
}
private ICompilationUnit getControllerClass(ElementChangedEvent event) {
IJavaElementDelta rootDelta = event.getDelta();
Stack<IJavaElementDelta> queue = new Stack<>();
queue.push(rootDelta);
while (!queue.isEmpty()) {
IJavaElementDelta delta = queue.pop();
IJavaElement element = delta.getElement();
if (element instanceof ICompilationUnit &&
((ICompilationUnit) element).getElementName().equals(controllerClassName)) {
return (ICompilationUnit) element;
}
for (IJavaElementDelta childDelta : delta.getAffectedChildren()) {
queue.push(childDelta);
}
}
return null;
}
}
package no.tobask.sb4e;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
public class Requestor extends ASTRequestor {
private List<ICompilationUnit> candidates = new ArrayList<>();
@Override
public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
ControllerCandidateChecker checker = new ControllerCandidateChecker();
ast.accept(checker);
if (checker.visitedCandidate()) {
candidates.add(source);
}
}
public List<ICompilationUnit> getCandidates() {
return new ArrayList<>(candidates);
}
}
......@@ -155,7 +155,8 @@ public class FXMLEditor extends EditorPart {
e.printStackTrace();
}
String controllerName = editorController.getFxomDocument().getFxomRoot().getFxController();
editorController.setGlossary(new JavaProjectGlossary(controllerName));
editorController.setGlossary(new JavaProjectGlossary(controllerName, fxmlUrl,
editorWindowController.infoPanelController));
canvas.setScene(editorWindowController.getScene());
editorController.getSelection().revisionProperty().addListener(editorSelectionListener);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment