diff --git a/sb/src/main/java/com/oracle/javafx/scenebuilder/app/info/InfoPanelController.java b/sb/src/main/java/com/oracle/javafx/scenebuilder/app/info/InfoPanelController.java index 04a74186ef148717e02fba66fb2b8cff8a12a911..82b07ecd06b11bc027498dff8081a2ef63eafe87 100644 --- a/sb/src/main/java/com/oracle/javafx/scenebuilder/app/info/InfoPanelController.java +++ b/sb/src/main/java/com/oracle/javafx/scenebuilder/app/info/InfoPanelController.java @@ -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. diff --git a/sb4e.examples/src/no/tobask/sb4e/examples/Controller.java b/sb4e.examples/src/no/tobask/sb4e/examples/Controller.java new file mode 100644 index 0000000000000000000000000000000000000000..86e27f97078be775f3788e3db30bbfcc4ed78f2d --- /dev/null +++ b/sb4e.examples/src/no/tobask/sb4e/examples/Controller.java @@ -0,0 +1,11 @@ +package no.tobask.sb4e.examples; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; + +public class Controller { + + @FXML + Label lbl; + +} diff --git a/sb4e/META-INF/MANIFEST.MF b/sb4e/META-INF/MANIFEST.MF index ec04ff542000439967edffbca9a9bb5327647d7d..c8f2577c4b3b101a5ffec29eacc3d8218d607d93 100644 --- a/sb4e/META-INF/MANIFEST.MF +++ b/sb4e/META-INF/MANIFEST.MF @@ -24,6 +24,7 @@ Import-Package: com.oracle.javafx.scenebuilder.app.selectionbar, org.eclipse.e4.core.di.annotations;version="1.6.0", org.eclipse.e4.ui.di, org.eclipse.jdt.core, + org.eclipse.jdt.core.dom, org.eclipse.jdt.launching Bundle-ActivationPolicy: lazy Bundle-ClassPath: . diff --git a/sb4e/src/no/tobask/sb4e/ControllerCandidateChecker.java b/sb4e/src/no/tobask/sb4e/ControllerCandidateChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..52892aa99e40787a434bb90ec7622113962f552c --- /dev/null +++ b/sb4e/src/no/tobask/sb4e/ControllerCandidateChecker.java @@ -0,0 +1,54 @@ +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; + } + +} diff --git a/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java b/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java index afabc3ef44c3bbb1f5fe8442160bcab52e1b2743..cab56772c7308cf8402f1b2483bcb4e472b141ae 100644 --- a/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java +++ b/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java @@ -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) { - IJavaProject project = JavaModelUtils.getJavaProjectFromUrl(fxmlLocation); - String packageName = JavaModelUtils.getPackageContainingFile(fxmlLocation).getElementName(); - List<ICompilationUnit> candidates = new ArrayList<>(); - for (IPackageFragment pkg : JavaModelUtils.getAllMatchingPackages(packageName, project)) { - candidates.addAll(getCandidateControllers(pkg, fxmlLocation)); + if (candidateControllers == null) { + IJavaProject project = JavaModelUtils.getJavaProjectFromUrl(fxmlLocation); + String packageName = JavaModelUtils.getPackageContainingFile(fxmlLocation).getElementName(); + candidateControllers = new ArrayList<>(); + for (IPackageFragment pkg : JavaModelUtils.getAllMatchingPackages(packageName, project)) { + 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,62 +91,83 @@ 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 CompilationUnit getAst(ICompilationUnit source) { + ASTParser parser = ASTParser.newParser(AST.JLS9); + parser.setSource(source); + return (CompilationUnit) parser.createAST(null); } - private boolean isCandidateControllerClass(ICompilationUnit javaClass, URL fxmlLocation) { - IType type = javaClass.findPrimaryType(); - return hasNoOrEmptyConstructor(type) && hasFxmlAnnotatedMembers(type); + 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 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 boolean inSameProject(ICompilationUnit clazz) { + return clazz.getJavaProject().equals(JavaModelUtils.getJavaProjectFromUrl(fxmlLocation)); } - - private boolean hasNoOrEmptyConstructor(IType type) { + + private List<ICompilationUnit> getCandidateControllers(IPackageFragment pkg) { + ASTParser parser = ASTParser.newParser(AST.JLS9); + Requestor requestor = new Requestor(); try { - for (IMethod method : type.getMethods()) { - if (method.isConstructor()) { - return method.getParameters().length == 0; - } - } + 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) { List<String> eventHandlers = new ArrayList<>(); IType type = controller.findPrimaryType(); @@ -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; - } - } diff --git a/sb4e/src/no/tobask/sb4e/Requestor.java b/sb4e/src/no/tobask/sb4e/Requestor.java new file mode 100644 index 0000000000000000000000000000000000000000..6d3db506eaa2c74521911d113ccbe13e99f46192 --- /dev/null +++ b/sb4e/src/no/tobask/sb4e/Requestor.java @@ -0,0 +1,27 @@ +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); + } + +} diff --git a/sb4e/src/no/tobask/sb4e/editors/FXMLEditor.java b/sb4e/src/no/tobask/sb4e/editors/FXMLEditor.java index 8178bb0d9b6f5046c877e375b6b9e1f390105686..b5840e2b863dc61ceb3de2738d260f1110267482 100644 --- a/sb4e/src/no/tobask/sb4e/editors/FXMLEditor.java +++ b/sb4e/src/no/tobask/sb4e/editors/FXMLEditor.java @@ -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);