public Object visit(Lookup node) {
            // check for type expression to resolve static fields against
            Expression expr = checkForTypeExpression(node.getExpression());
            if (expr == null) {
                return null;
            }

            // validate expression
            node.setExpression(expr);
            check(expr);

            // now check if expression type class contains the lookup name
            Type type = expr.getType();
            String lookupName = node.getLookupName().getName();

            if (type != null && lookupName != null) {
                Class<?> clazz = type.getObjectClass();
                // Lookup can only work on objects.
                type = type.toNonPrimitive();
                expr.convertTo(type);

                if (expr instanceof TypeExpression) {
                    String property = node.getLookupName().getName();

                    // check if constant class
                    if ("class".equals(property)) {
                        node.setReadProperty(null);
                        node.setType(new Type(expr.getType().getNaturalClass().getClass()));
                    }

                    // lookup field and error out if invalid
                    else {
                        Field field = null;
                        try { field = clazz.getField(property); }
                        catch (Exception exception) {
                            error("property.not.found", node);
                            return null;
                        }

                        // ensure field is static
                        if (!Modifiers.isStatic(field.getModifiers())) {
                            error("field.not.static", node);
                            return null;
                        }

                        // save static field and type
                        node.setReadProperty(field);
                        node.setType(new Type(field.getType(),
                                              field.getGenericType()));
                    }

                    // return success
                    return null;
                }
                else if ("length".equals(lookupName)) {
                    if (clazz == String.class) {
                        node.setType(Type.INT_TYPE);
                        try {
                            node.setReadMethod(clazz.getMethod("length"));
                            return null;
                        }
                        catch (NoSuchMethodException e) {
                            throw new LinkageError(e.toString());
                        }
                    }
                    else if (Collection.class.isAssignableFrom(clazz) ||
                             Map.class.isAssignableFrom(clazz)) {
                        node.setType(Type.INT_TYPE);
                        try {
                            node.setReadMethod(clazz.getMethod("size"));
                            return null;
                        }
                        catch (NoSuchMethodException e) {
                            throw new LinkageError(e.toString());
                        }
                    }
                    else if (clazz.isArray()) {
                        node.setType(Type.INT_TYPE);
                        return null;
                    }
                    // TODO: consider checking if public field named
                }

                // attempt to lookup by property
                Map<String, PropertyDescriptor> properties;
                try {
                    properties =
                        BeanAnalyzer.getAllProperties(type.getGenericType());
                }
                catch (IntrospectionException e) {
                    error(e.toString(), node);
                    return null;
                }

                PropertyDescriptor prop = properties.get(lookupName);
                // TODO: consider checking if public field named lookupName
                if (prop == null) {
                    error("lookup.undefined", lookupName, type.getSimpleName(),
                          node.getLookupName());
                    return null;
                }

                Type nodeType = null;
                if (prop instanceof GenericPropertyDescriptor) {
                    nodeType = new Type(((GenericPropertyDescriptor) prop)
                                            .getGenericPropertyType());
                }

                Method rmethod = null;
                if (prop != null) {
                    rmethod = prop.getReadMethod();
                    if (rmethod == null) {
                        error("lookup.unreadable", lookupName,
                              node.getLookupName());
                        return null;
                    }

                    Class<?> retClass = rmethod.getReturnType();
                    if (retClass == null) {
                        error("lookup.array.only", lookupName,
                              node.getLookupName());
                        return null;
                    }

                    if (nodeType == null) {
                        TypedElement te = null;
                        java.lang.reflect.Type ge = null;
                        if (rmethod != null) {
                            ge = rmethod.getGenericReturnType();
                            te = rmethod.getAnnotation(TypedElement.class);
                        }

                        nodeType = (te != null ? new Type(retClass, ge, te)
                                               : new Type(retClass, ge));
                    }
                }

                Class<?> nodeClass = nodeType.getNaturalClass();
                Type genericType = Generics.findType(nodeType, type);
                if (genericType != null) {
                    nodeClass = nodeType.getObjectClass();
                    node.setType(Type.OBJECT_TYPE);
                }

                checkAccess(nodeClass, node.getLookupName());

                if (genericType == null) {
                    node.setType(nodeType);
                }
                else {
                    node.forceConversion(genericType, true);
                }

                if (nodeClass == char.class) {
                    // Convert any returned char to a String.
                    node.convertTo(Type.NON_NULL_STRING_TYPE);
                }
                else if (nodeClass == Character.class) {
                    // Convert any returned Character to a String.
                    node.convertTo(Type.STRING_TYPE);
                }

                if (ClassUtils.isDeprecated(rmethod)) {
                    warn("functioncallexpression.deprecated", lookupName, node);
                }

                node.setReadMethod(rmethod);
            }

            return null;
        }