package org.uva.student.calinwouter.qlqls.qls; import org.uva.student.calinwouter.qlqls.generated.analysis.ReversedDepthFirstAdapter; import org.uva.student.calinwouter.qlqls.generated.node.*; import org.uva.student.calinwouter.qlqls.ql.interfaces.ITypeDescriptor; import org.uva.student.calinwouter.qlqls.ql.staticfieldscollector.PTypeCollector; import org.uva.student.calinwouter.qlqls.qls.exceptions.CouldNotFindMatchingQLSComponentException; import java.lang.reflect.InvocationTargetException; import java.util.*; import static org.uva.student.calinwouter.qlqls.ql.helper.ASTHelper.*; /** * This adapter parses the syntax reverse depth-first and creates a corresponding internal model of the results, * using the functions defined in the COMPONENTS_PACKAGE_PREFIX location. This way, QLS is extremely flexible, * as the user can simply add new functions whenever required, without touching the syntax. * * Note that the GUI and QLS are completely separated, and that QLS is also completely separated * from the QL Interpreter. */ public class QLSAdapter extends ReversedDepthFirstAdapter { private final PTypeCollector pTypeCollector = new PTypeCollector(); private final static List<String> allowablePaths = new LinkedList<String>(); /* This is the relative package where the functions reside. */ private final static String COMPONENTS_PACKAGE_PREFIX = QLSAdapter.class.getPackage().getName() + ".model.functions."; /* This is the relative package where the functions reside. */ private final static String WIDGETS_PACKAGE_PREFIX = QLSAdapter.class.getPackage().getName() + ".model.functions.widgets."; /* The stack is used for pushing parameters of functions and the hashmaps to the stack, and forming a model * by popping the elements of the stack and putting them into the constructor of the corresponding class. */ private final Stack<Object> argumentStack = new Stack<Object>(); /** * This method creates a new component or widget dynamically using QLSReflection. */ private Object interopComponent(String componentName, List<Object> args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, CouldNotFindMatchingQLSComponentException { final String classPath = firstCharacterToUpper(componentName); QLSReflection qlsReflection = new QLSReflection(allowablePaths); return qlsReflection.newComponentInstance(classPath, args); } /** * Get the value of the first object on the stack. * * Example of correct usage: * - Parse a AStylesheet-object using the apply-method on the abstract syntax tree's method. * - This method returns the transformed stylesheet (StyleSheet)-object. * @return the value of the first object on the stack. */ public Object popValue() { assert (argumentStack.size() == 1); return pop(); } /** * ANonParameterizedFunction-s are functions in the form of: * * fn () */ @Override public void outANonParameterizedFunction(ANonParameterizedFunction node) { ArrayList<Object> values = new ArrayList<Object>(); final Object model; try { final String identifier = getIdentifier(node); model = interopComponent(identifier, values); push(model); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (CouldNotFindMatchingQLSComponentException e) { throw new RuntimeException(e); } } /** * AParameterizedFunction-s are functions of the form of: * * ident (expr_1, ... expr_n), with n > 0. */ @Override public void outAParameterizedFunction(AParameterizedFunction node) { ArrayList<Object> values = new ArrayList<Object>(); for (int i = 0; i < node.getElement().size(); i++) { values.add(pop()); } try { final String identifier = getIdentifier(node); final Object model = interopComponent(identifier, values); push(model); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (CouldNotFindMatchingQLSComponentException e) { throw new RuntimeException(e); } } /** * AIdentifierElement-s are identifier representations (e.g. variables or references). */ @Override public void outAIdentifierElement(AIdentifierElement node) { final String identifier = getIdentifier(node); push(identifier); } /** * AHexElement-s are number representations in hexadecimal format. */ @Override public void outAHexElement(AHexElement node) { final Integer hexValue = getHex(node); push(hexValue); } /** * AStringElement-s are string representations. */ @Override public void outAStringElement(AStringElement node) { final String string = getString(node); push(string); } private ITypeDescriptor convertAstTypeToTypeDescriptor(PType nodeAstType) { nodeAstType.apply(pTypeCollector); return pTypeCollector.popType(); } /** * ATypeElement-s are type representations (i.e. int, boolean, string). */ @Override public void outATypeElement(ATypeElement node) { final PType nodeAstType = node.getType(); final ITypeDescriptor nodeTypeDescriptor = convertAstTypeToTypeDescriptor(nodeAstType); push(nodeTypeDescriptor); } /** * ANumberElement-s are number representations. */ @Override public void outANumberElement(ANumberElement node) { final Integer elementAsNumber = getNumber(node); push(elementAsNumber); } /** * AObjectElement-s are key-value maps * * Syntax: * * '{' AObjectEl* '}' * * => * * HashMap<Object, Object> * * In case the elements on the stack deviate from the SimpleEntry-type, this method raises a ClassCastException. */ @Override @SuppressWarnings("unchecked") // Required for reflection. public void outAObjectElement(AObjectElement node) { Map<Object, Object> hashMap = new HashMap<Object, Object>(); for (int i = 0; i < node.getKeyValue().size(); i++) { final AbstractMap.SimpleEntry<Object, Object> entry = (AbstractMap.SimpleEntry<Object, Object>) pop(); final Object keyObject = entry.getKey(); final String key = keyObject.toString(); final Object value = entry.getValue(); hashMap.put(key, value); } push(hashMap); } /** * AKeyValue-s are the entries in the AObjectElement's key-value map. * * Syntax: * * (Any)Element ':' (Any)Element * * => * * SimpleEntry<Object, Object> */ @Override public void outAKeyValue(AKeyValue node) { AbstractMap.SimpleEntry<Object, Object> mapEntry = new AbstractMap.SimpleEntry<Object, Object>(pop(), pop()); push(mapEntry); } private Object pop() { return argumentStack.pop(); } private void push(Object toPush) { argumentStack.push(toPush); } private static String firstCharacterToUpper(String str) { assert(str.length() > 0); final String firstCharacter = str.substring(0, 1); final String rest = str.substring(1); return firstCharacter.toUpperCase() + rest; } /** QLSAdapter can only be used through the QLSInterpreter and the QLSTypeChecker. */ protected QLSAdapter() {} static { allowablePaths.add(COMPONENTS_PACKAGE_PREFIX); allowablePaths.add(WIDGETS_PACKAGE_PREFIX); } }