diff --git a/sb4e.examples/src/no/tobask/sb4e/examples/GreatFxml.fxml b/sb4e.examples/resources/no/tobask/sb4e/examples/GreatFxml.fxml similarity index 100% rename from sb4e.examples/src/no/tobask/sb4e/examples/GreatFxml.fxml rename to sb4e.examples/resources/no/tobask/sb4e/examples/GreatFxml.fxml diff --git a/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java b/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java index 09b0052df573afe7ef3897c383310461e4fd20a6..e472c69333f6b31cbc1eebdfe1f40a5764d84f8c 100644 --- a/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java +++ b/sb4e/src/no/tobask/sb4e/JavaProjectGlossary.java @@ -2,68 +2,248 @@ 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.core.resources.IFile; +import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IElementChangedListener; +import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.ILocalVariable; +import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +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 com.oracle.javafx.scenebuilder.kit.glossary.Glossary; -public class JavaProjectGlossary extends Glossary { +public class JavaProjectGlossary extends Glossary implements IElementChangedListener { + + private String controllerClassName; + private Map<String, List<String>> fxIds; + private List<String> eventHandlers; + + public JavaProjectGlossary() { + JavaCore.addElementChangedListener(this, ElementChangedEvent.POST_CHANGE); + } @Override public List<String> queryControllerClasses(URL fxmlLocation) { - IJavaProject javaProject = javaProjectFromUrl(fxmlLocation); - ArrayList<String> classes = new ArrayList<String>(); - try { - String packagePath = "/" + javaProject.getProject().getName() + "/src"; - IPackageFragmentRoot root = javaProject.findPackageFragmentRoot(new Path(packagePath)); - for (IJavaElement child : root.getChildren()) { - if (child instanceof IPackageFragment) { - IPackageFragment fragment = (IPackageFragment) child; - for (ICompilationUnit javaClass : fragment.getCompilationUnits()) { - if (isControllerClass(javaClass)) { - classes.add(javaClass.getElementName()); - } - } - } - } - } catch (JavaModelException jme) { - jme.printStackTrace(); + IJavaProject project = getJavaProjectFromUrl(fxmlLocation); + String packageName = getPackageContainingFile(fxmlLocation).getElementName(); + List<ICompilationUnit> candidates = new ArrayList<>(); + for (IPackageFragment pkg : getAllMatchingPackages(packageName, project)) { + candidates.addAll(getCandidateControllers(pkg, fxmlLocation)); } - return classes; + return candidates.stream().map(c -> c.getElementName()).collect(Collectors.toList()); } - - private boolean isControllerClass(ICompilationUnit javaClass) { - return true; + + @Override + public List<String> queryFxIds(URL fxmlLocation, String controllerClass, Class<?> targetType) { + if (controllerClassName == null) { + controllerClassName = controllerClass; + } + if (fxIds == null) { + ICompilationUnit controller = discoverController(fxmlLocation, controllerClass); + fxIds = controller == null ? getFxIds(controller) : new HashMap<>(); + } + return fxIds.getOrDefault(getClassName(targetType), new ArrayList<>()); + } + + @Override + public List<String> queryEventHandlers(URL fxmlLocation, String controllerClass) { + if (controllerClassName == null) { + controllerClassName = controllerClass; + } + if (eventHandlers == null) { + ICompilationUnit controller = discoverController(fxmlLocation, controllerClass); + eventHandlers = controller == null ? + getEventHandlers(controller) : new ArrayList<>(); + } + return eventHandlers; } - private IJavaProject javaProjectFromUrl(URL url) { + @Override + public void elementChanged(ElementChangedEvent event) { + if (controllerClassName != null) { + ICompilationUnit controllerClass = getControllerClass(event); + if (controllerClass != null) { + fxIds = getFxIds(controllerClass); + eventHandlers = getEventHandlers(controllerClass); + } + } + } + + private IJavaProject getJavaProjectFromUrl(URL url) { IPath path = new Path(url.getPath()); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IFile file = workspaceRoot.getFileForLocation(path); return JavaCore.create(file.getProject()); } - @Override - public List<String> queryFxIds(URL fxmlLocation, String controllerClass, Class<?> targetType) { - // TODO Auto-generated method stub - return new ArrayList<String>(); + private IPackageFragment getPackageContainingFile(URL fxmlLocation) { + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IPath path = new Path(fxmlLocation.getPath()).removeLastSegments(1); + IFolder folder = (IFolder) workspaceRoot.getContainerForLocation(path); + return (IPackageFragment) JavaCore.create(folder); } - @Override - public List<String> queryEventHandlers(URL fxmlLocation, String controllerClass) { - // TODO Auto-generated method stub - return new ArrayList<String>(); + private Collection<IPackageFragment> getAllMatchingPackages(String packageName, + IJavaProject project) { + Collection<IPackageFragment> packages = new ArrayList<>(); + for (IPackageFragmentRoot folder : getSourceFolders(project)) { + IPackageFragment pkg = folder.getPackageFragment(packageName); + if (pkg.exists()) { + packages.add(pkg); + } + } + return packages; + } + + private List<IPackageFragmentRoot> getSourceFolders(IJavaProject project) { + List<IPackageFragmentRoot> sourceFolders = new ArrayList<>(); + try { + for (IPackageFragmentRoot root : project.getAllPackageFragmentRoots()) { + if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { + sourceFolders.add(root); + } + } + } catch (JavaModelException e) { + e.printStackTrace(); + } + return sourceFolders; + } + + 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 isCandidateControllerClass(ICompilationUnit javaClass, URL fxmlLocation) { + return true; + } + + private ICompilationUnit discoverController(URL fxmlLocation, String controllerName) { + String packageName = getPackageContainingFile(fxmlLocation).getElementName(); + IJavaProject project = getJavaProjectFromUrl(fxmlLocation); + for (IPackageFragment pkg : getAllMatchingPackages(packageName, project)) { + ICompilationUnit clazz = pkg.getCompilationUnit(controllerName); + if (clazz.exists()) { + return clazz; + } + } + return null; + } + + private String getClassName(Class<?> clas) { + int classNameStartIdx = clas.getName().lastIndexOf(".") + 1; + return clas.getName().substring(classNameStartIdx); + } + + private List<String> getEventHandlers(ICompilationUnit controller) { + List<String> eventHandlers = new ArrayList<>(); + IType type = controller.findPrimaryType(); + try { + for (IMethod method : type.getMethods()) { + if (isEventHandler(method)) { + eventHandlers.add(method.getElementName()); + } + } + } catch (JavaModelException e) { + e.printStackTrace(); + } + return eventHandlers; + } + + private boolean isEventHandler(IMethod method) { + boolean isFxmlAnnotated = method.getAnnotation("FXML").exists(); + try { + boolean returnsVoid = method.getReturnType().equals("V"); + ILocalVariable firstParameter = method.getParameters()[0]; + IType declaringType = method.getDeclaringType(); + String typeSignature = firstParameter.getTypeSignature(); + String simpleName = Signature.getSignatureSimpleName(typeSignature); + String[][] resolvedNames = declaringType.resolveType(simpleName); + String qualifiedName = Signature.toQualifiedName(resolvedNames[0]); + } catch (JavaModelException e) { + e.printStackTrace(); + } + return false; + } + + private Map<String, List<String>> getFxIds(ICompilationUnit controller) { + Map<String, List<String>> ids = new HashMap<>(); + IType type = controller.findPrimaryType(); + try { + for (IField field : type.getFields()) { + if (field.getAnnotation("FXML").exists()) { + addToIds(ids, field); + } + } + } catch (JavaModelException e) { + e.printStackTrace(); + } + return ids; + } + + private void addToIds(Map<String, List<String>> ids, IField field) { + try { + String typeName = field.getTypeSignature(); + typeName = typeName.substring(1, typeName.length()-1); + String fieldName = field.getElementName(); + List<String> existingIds = ids.putIfAbsent(typeName, + new ArrayList<>(Arrays.asList(fieldName))); + if (existingIds != null) { + existingIds.add(fieldName); + } + } catch (JavaModelException e) { + e.printStackTrace(); + } + } + + 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; } }