package com.mattc.autotyper.meta; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ClassInfo; import com.mattc.autotyper.util.Console; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import java.util.Scanner; @FXCompatible public class NodeParser { private static final BiMap<String, Class<? extends Node>> nodeMap = HashBiMap.create(); static { try { findParseableNodes(nodeMap); } catch (final ClassNotFoundException e) { Console.exception(e); } // Non-API Parseables nodeMap.put("%b", Button.class); nodeMap.put("%t", TextField.class); nodeMap.put("%c", ComboBox.class); nodeMap.put("%B", ToggleButton.class); } public List<Node> parse(String format) { final Scanner scan = new Scanner(format); final List<Node> nodes = Lists.newArrayList(); final StringBuilder sb = new StringBuilder(); while (scan.hasNext()) { final String temp = scan.next(); if (nodeMap.containsKey(temp)) { try { nodes.add(new Label(sb.append(" ").toString())); nodes.add(createInstanceOf(nodeMap.get(temp))); nodes.add(new Label(" ")); sb.setLength(0); sb.trimToSize(); } catch (InstantiationException | IllegalAccessException e) { Console.exception(e); } } else if (temp.startsWith("%")) { scan.close(); throw new IllegalStateException("Tag is in Tag Format but is not a VALID Tag! -- " + temp); } else { sb.append(temp).append(" "); } } nodes.add(new Label(sb.toString())); scan.close(); return nodes; } private Node createInstanceOf(Class<? extends Node> klass) throws InstantiationException, IllegalAccessException { try { boolean unlocked = false; final Constructor<? extends Node> con = klass.getDeclaredConstructor(); if ((unlocked = !con.isAccessible())) { con.setAccessible(true); } con.setAccessible(true); final Node val = con.newInstance(); if (unlocked) { con.setAccessible(false); } return val; } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException("No Private/Protected/Public No-Arg Constructor to Initialize!", e); } } @SuppressWarnings("unchecked") private static final void findParseableNodes(Map<String, Class<? extends Node>> map) throws ClassNotFoundException { try { final ImmutableSet<ClassInfo> klasses = ClassPath.from(ClassLoader.getSystemClassLoader()).getTopLevelClassesRecursive("com.mattc"); for (final ClassInfo ci : klasses) { final Class<?> klass = Class.forName(ci.getName()); final FXParseable anno = klass.getAnnotation(FXParseable.class); if ((anno != null) && !Node.class.isAssignableFrom(klass)) throw new IllegalStateException("Non-Parseable Class tagged with @FXParseable! A Parseable Class is one that inherits of Node..."); if ((anno == null) || !Node.class.isAssignableFrom(klass)) { continue; } String tag = anno.value(); if (tag.equals(FXParseable.NO_VAL)) { tag = NodeParser.createTagFor(klass); } else if (!tag.startsWith("%")) throw new IllegalArgumentException("Attempted to set tag to " + tag + " but a tag must start with a '%'!"); else if (map.containsKey(tag)) throw new IllegalArgumentException("Attempt to set tag to " + tag + " for " + klass.getSimpleName() + " but it conflicts with another tag!"); Console.info("Add FXParseable " + ci.getName() + " as " + tag); map.put(tag, (Class<? extends Node>) klass); } } catch (final IOException e) { Console.exception(e); } } public static final String createTagFor(Class<?> klass) { final char[] name = klass.getSimpleName().toCharArray(); final StringBuilder sb = new StringBuilder("%"); for (final char c : name) if (Character.isUpperCase(c)) { sb.append(c); } return sb.toString().toLowerCase(); } public static final Class<? extends Node> getClassFor(String tag) { return nodeMap.get(tag); } public static final String getTagFor(Class<? extends Node> klass) { return nodeMap.inverse().get(klass); } }