nl.tue.gale.ae.grapple.CAMFormat.java Source code

Java tutorial

Introduction

Here is the source code for nl.tue.gale.ae.grapple.CAMFormat.java

Source

/*
    
   This file is part of GALE (Generic Adaptation Language and Engine).
    
GALE is free software: you can redistribute it and/or modify it under the 
terms of the GNU Lesser General Public License as published by the Free 
Software Foundation, either version 3 of the License, or (at your option) 
any later version.
    
GALE is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for 
more details.
    
You should have received a copy of the GNU Lesser General Public License
along with GALE. If not, see <http://www.gnu.org/licenses/>.
    
 */
/**
 * CAMFormat.java
 * Last modified: $Date$
 * In revision:   $Revision$
 * Modified by:   $Author$
 *
 * Copyright (c) 2008-2011 Eindhoven University of Technology.
 * All Rights Reserved.
 *
 * This software is proprietary information of the Eindhoven University
 * of Technology. It may be used according to the GNU LGPL license.
 */
package nl.tue.gale.ae.grapple;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import nl.tue.gale.common.GaleUtil;
import nl.tue.gale.common.cache.Cache;
import nl.tue.gale.common.cache.Caches;
import nl.tue.gale.common.parser.ParseNode;
import nl.tue.gale.common.uri.URI;
import nl.tue.gale.common.uri.URIs;
import nl.tue.gale.dm.GAMFormat;
import nl.tue.gale.dm.data.Attribute;
import nl.tue.gale.dm.data.Concept;
import nl.tue.gale.dm.data.ConceptRelation;

import org.dom4j.Element;

import com.google.common.collect.ImmutableList;

public class CAMFormat {
    private Map<URI, Concept> conceptMap = new HashMap<URI, Concept>();
    private Map<String, CRT> crtMap = new HashMap<String, CRT>();
    private Cache<Concept> dm = Caches.newCache(10);
    private Map<URI, URI> conceptIdToName = new HashMap<URI, URI>();

    public static List<Concept> getConcepts(String cam) {
        return getConcepts(GaleUtil.parseXML(new StringReader(cam)).getRootElement());
    }

    public static List<Concept> getConcepts(Element cam) {
        CAMFormat camFormat = new CAMFormat();
        try {
            return camFormat.getInternalConcepts(cam);
        } catch (NullPointerException npe) {
            throw new IllegalArgumentException("invalid CAM", npe);
        }
    }

    @SuppressWarnings("unchecked")
    private List<Concept> getInternalConcepts(Element cam) {
        URI baseURI = URIs
                .of("gale://gale.tue.nl/cam/" + replaceSpace(cam.element("header").elementText("title")) + "/");

        List<Element> models = cam.element("body").element("cam").element("camInternal").element("domainModel")
                .elements("model");
        for (Element model : models) {
            Element vdex = model.element("body").element("dm").element("vdex");
            for (Element termElement : (Collection<Element>) vdex.elements("term")) {
                Concept c = new Concept(
                        baseURI.resolve(replaceSpace(termElement.element("caption").elementText("langstring"))));
                conceptIdToName.put(baseURI.resolve(termElement.elementText("termIdentifier")), c.getUri());
                conceptMap.put(c.getUri(), c);
                c.setProperty("cam.model.guid", cam.element("header").elementText("modeluuid"));
                c.setProperty("cam.concept.guid", termElement.elementText("termIdentifier"));
                Attribute a;
                try {
                    a = makeAttribute("resource", resourceCode(termElement), "", "java.lang.String", false);
                    addResourceAttributes(a, termElement);
                    c.addAttribute(a);
                } catch (Exception e) {
                }
                c.setProperty("title", termElement.element("caption").elementText("langstring"));
                if (termElement.elements("metadata").size() > 0)
                    addConceptProperties(c, termElement.element("metadata"));
            }
            for (Element relElement : (Collection<Element>) vdex.elements("relationship")) {
                ConceptRelation cr = new ConceptRelation(relElement.elementText("relationshipType"));
                cr.changeInConcept(
                        conceptMap.get(conceptIdToName.get(baseURI.resolve(relElement.elementText("sourceTerm")))));
                cr.changeOutConcept(
                        conceptMap.get(conceptIdToName.get(baseURI.resolve(relElement.elementText("targetTerm")))));
            }
        }
        Element crts = cam.element("body").element("cam").element("camInternal").element("crtModel");
        for (Element crtElement : (Collection<Element>) crts.elements("model")) {
            CRT crt = CRT.parse(crtElement);
            crtMap.put(crt.getUid(), crt);
        }
        for (Element crtElement : (Collection<Element>) cam.element("body").element("cam").element("camInternal")
                .elements("crt"))
            interpretCRT(crtElement, baseURI);
        List<Concept> resultList = new LinkedList<Concept>();
        resultList.addAll(conceptMap.values());
        for (Concept c : resultList) {
            if (c.getAttribute("visited") == null) {
                Attribute a = makeAttribute("visited", "0", "", "java.lang.Integer", true);
                c.addAttribute(a);
            }
            if (c.getAttribute("suitability") == null) {
                Attribute a = makeAttribute("suitability", "true", "", "java.lang.Boolean", false);
                c.addAttribute(a);
            }
            for (Attribute a : c.getAttributes()) {
                String pubString = a.getProperty("public");
                if (pubString != null && !"false".equals(pubString))
                    a.setProperty("persistent", "true");
            }
        }
        return resultList;
    }

    private void addResourceAttributes(Attribute a, Element termElement) {
        @SuppressWarnings("unchecked")
        List<Element> rlist = (List<Element>) termElement.elements("mediaDescriptor");
        rlist = ImmutableList.copyOf(rlist);
        for (Element urlElement : rlist) {
            String label = getResourceProperty(termElement, urlElement, "label");
            if (label != null)
                a.setProperty(label, urlElement.elementText("mediaLocator"));
        }
    }

    private String getResourceProperty(Element termElement, Element resourceElement, String name) {
        String guid = null;
        try {
            guid = resourceElement.element("interpretationNote").elementText("langstring");
        } catch (Exception e) {
        }
        String result = null;
        try {
            @SuppressWarnings("unchecked")
            List<Element> metadata = (List<Element>) termElement.element("metadata").elements("resource");
            for (Element metaElement : metadata)
                if ((metaElement.attributeValue("id").equals(guid)) && (metaElement.element("lom")
                        .element("general").element("title").elementText("langstring").equals(name))) {
                    result = metaElement.element("lom").element("general").element("description")
                            .elementText("langstring");
                }
        } catch (Exception e) {
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private String resourceCode(Element termElement) {
        List<Element> rlist = new ArrayList<Element>();
        rlist.addAll(termElement.elements("mediaDescriptor"));
        Collections.reverse(rlist);
        if (rlist.size() == 0)
            return "\"gale:/empty.xhtml\"";
        if (rlist.size() == 1)
            return "\"" + rlist.get(0).elementText("mediaLocator") + "\"";
        StringBuilder result = new StringBuilder();
        result.append("\"gale:/empty.xhtml\"");
        for (Element urlElement : rlist) {
            String expr = getResourceProperty(termElement, urlElement, "expr");
            if (expr == null)
                expr = "true";

            // build part of resource string
            result.insert(0, "\":");
            result.insert(0, urlElement.elementText("mediaLocator"));
            result.insert(0, "?\"");
            result.insert(0, expr);
            result.insert(0, "(");
            result.append(")");
        }
        return result.toString();
    }

    @SuppressWarnings("unchecked")
    private void addConceptProperties(Concept c, Element element) {
        for (Element prop : (List<Element>) element.elements("concept")) {
            Element entry = prop.element("lom").element("general").element("catalogentry");
            String name = entry.elementText("catalog");
            String value = entry.element("entry").elementText("langstring");
            c.setProperty(name, value);
        }
    }

    private String replaceSpace(String s) {
        // TODO: replace all strange characters
        try {
            return /* URLEncoder.encode( */s.replace(" ", "_")/* , "UTF-8") */;
        } catch (Exception e) {
            e.printStackTrace();
            return s.replace(" ", "_");
        }
    }

    @SuppressWarnings("unchecked")
    private void interpretCRT(Element crtElement, URI baseURI) {
        CRT crt = crtMap.get(crtElement.elementText("uuid"));
        if (crt == null)
            throw new IllegalArgumentException(
                    "the CRT '" + crtElement.elementText("uuid") + "' is referenced but not included");
        Map<String, Set<URI>> socketData = new HashMap<String, Set<URI>>();
        for (Element crtSocketElement : (Collection<Element>) crtElement.elements("camSocket")) {
            CRTsocket socket = crt.getSocketById(crtSocketElement.elementText("socketid"));
            Set<URI> cList = new HashSet<URI>();
            socketData.put(socket.getUid(), cList);
            for (Element entityElement : (Collection<Element>) crtSocketElement.elements("entity")) {
                Concept c = conceptMap.get(conceptIdToName.get(baseURI.resolve(entityElement.elementText("dmId"))));
                if (c == null)
                    throw new IllegalArgumentException("unable to find DM concept with ID '"
                            + entityElement.elementText("dmId") + "' mentioned in CRT '" + crt.getName() + "'");
                cList.add(c.getUri());
            }
        }
        for (CRTvar var : crt.getVariables()) {
            Set<URI> cList = socketData.get(crt.getSocketByName(var.getSocketName()).getUid());
            if (cList == null)
                throw new IllegalArgumentException(
                        "the CRT '" + crt.getName() + "' has UM variables that refer to non-existing sockets");
            for (URI cname : cList) {
                Concept c = conceptMap.get(cname);
                if (c.getAttribute(var.getName()) == null) {
                    Attribute attr = makeAttribute(var.getName(), var.getDefaultVar(), "", var.getJavaType(),
                            var.isPersistent());
                    if (var.getRange() != null)
                        attr.setProperty("gumf.range", var.getRange());
                    if (var.isPublicVar()) {
                        attr.setProperty("persistent", "true");
                        attr.setProperty("public", c.getUri().resolve("#" + attr.getName()).toString());
                        attr.setProperty("authorative", (var.isPersistent() ? "true" : "false"));
                    }
                    c.addAttribute(attr);
                } else {
                    if (!var.getJavaType().equals(c.getAttribute(var.getName()).getType()))
                        throw new IllegalArgumentException("attribute type mismatch in CRT '" + crt.getName()
                                + "' (" + var.getName() + "): '" + var.getJavaType() + "' cannot be assigned to '"
                                + c.getAttribute(var.getName()).getType() + "'");
                }
            }
        }
        Pointer<String, URI> socketPointer = null;
        try {
            socketPointer = new Pointer<String, URI>(socketData);
        } catch (Exception e) {
            throw new IllegalArgumentException("empty socket in CRT '" + crt.getName() + "'", e);
        }
        String crtGUID = GaleUtil.newGUID();
        while (socketPointer.hasPointer()) {
            String code = crt.getCode();
            Map<String, URI> current = socketPointer.current();
            for (String socketId : current.keySet()) {
                CRTsocket s = crt.getSocketById(socketId);
                try {
                    code = code.replace("%" + s.getName() + "%", current.get(socketId).toString());
                } catch (Exception e) {
                    throw new IllegalArgumentException(
                            "the CRT '" + crt.getName() + "' has code that refers to socket '" + s.getName()
                                    + "' that does not exist in the definition",
                            e);
                }
            }
            for (Element param : (List<Element>) crtElement.elements("parameter")) {
                String pname = param.attributeValue("name");
                String pvalue = param.getText();
                code = code.replace("%" + pname + "%", pvalue);
            }
            try {
                List<Concept> codeConcepts = GAMFormat.readGAM(code, baseURI, dm);
                for (Concept c : codeConcepts)
                    interpretConceptAdditions(c, crtGUID);
            } catch (Exception e) {
                throw new IllegalArgumentException(
                        "the CRT '" + crt.getName() + "' has errors in its code: " + e.getMessage(), e);
            }
            socketPointer.next();
        }
    }

    private void interpretConceptAdditions(Concept c, String crtGUID) {
        Concept original = conceptMap.get(c.getUri());
        if (original == null) {
            original = new Concept(c.getUri());
            conceptMap.put(original.getUri(), original);
        }
        // properties
        Set<String> props = new HashSet<String>();
        for (String s : c.getProperties().keySet())
            if (!s.startsWith("~"))
                props.add(s);
        if (c.getEventCode() != null && !"".equals(c.getEventCode())) {
            props.add("event");
            c.setProperty("event", c.getEventCode());
        }
        compileProperties(original.getProperties(), props, c.getProperties());
        // relations
        for (ConceptRelation cr : c.getInCR()) {
            if (conceptMap.containsKey(cr.getInConcept().getUri()))
                compileCR(cr.getInConcept().getUri(), cr.getName(), c.getUri(), cr.getProperties());
        }
        for (ConceptRelation cr : c.getOutCR()) {
            if (conceptMap.containsKey(cr.getOutConcept().getUri()))
                compileCR(c.getUri(), cr.getName(), cr.getOutConcept().getUri(), cr.getProperties());
        }
        // attributes
        for (Attribute a : c.getAttributes()) {
            Attribute orgAttr = original.getAttribute(a.getName());
            if (orgAttr == null) {
                orgAttr = makeAttribute(a.getName(), "", "", a.getType(), a.isPersistent());
                original.addAttribute(orgAttr);
            }
            interpretAttributeAdditions(orgAttr, a, crtGUID);
        }
    }

    private void interpretAttributeAdditions(Attribute original, Attribute a, String crtGUID) {
        // some checks
        ParseNode node = (ParseNode) a.getTransientData("node");
        String nodeName = (String) node.get("name");
        if (nodeName.indexOf(":") < 0) // untyped attribute addition
            a.setType(original.getType());
        a.getProperties().remove("~extends");
        // check for type mismatch
        if (!original.getType().equals(a.getType()))
            throw new IllegalArgumentException("attribute type mismatch (" + original.getName() + "): '"
                    + a.getType() + "' cannot be assigned to '" + original.getType() + "'");
        // default code
        try {
            String nodeValue = (String) node.get("value");
            if (nodeValue != null && !"".equals(nodeValue.trim())) {
                char operation = (Character) node.get("operation");
                if (operation == '=')
                    original.setDefaultCode(a.getDefaultCode());
                else {
                    String opstr = ((operation == '|' || operation == '&') ? " " + operation + operation
                            : " " + operation) + " ";
                    StringBuilder newDefaultCode = new StringBuilder(
                            original.getDefaultCode() == null ? "" : original.getDefaultCode().trim());
                    if (original.getType().equals("java.lang.Boolean")) {
                        boolean initialized = "true".equals(original.getTransientData(crtGUID));
                        if (!initialized) {
                            if (newDefaultCode.length() > 0)
                                newDefaultCode.append(" && ");
                            newDefaultCode.append("(");
                            newDefaultCode.append(a.getDefaultCode());
                            newDefaultCode.append(")");
                            original.addTransientData(crtGUID, "true");
                        } else {
                            newDefaultCode.deleteCharAt(newDefaultCode.length() - 1);
                            newDefaultCode.append(opstr);
                            newDefaultCode.append(a.getDefaultCode());
                            newDefaultCode.append(")");
                        }
                    } else if (newDefaultCode.length() > 0) {
                        newDefaultCode.insert(0, "new " + original.getType() + "(");
                        newDefaultCode.append(opstr);
                        newDefaultCode.append(a.getDefaultCode());
                        newDefaultCode.append(")");
                    } else
                        newDefaultCode.append(a.getDefaultCode());
                    original.setDefaultCode(newDefaultCode.toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // properties
        Set<String> props = new HashSet<String>();
        for (String s : a.getProperties().keySet())
            if (!s.startsWith("~") && !"persistent".equals(s))
                props.add(s);
        if (a.getEventCode() != null && !"".equals(a.getEventCode())) {
            props.add("event");
            a.setProperty("event", a.getEventCode());
        }
        compileProperties(original.getProperties(), props, a.getProperties());
    }

    private void compileCR(URI source, String name, URI target, Map<String, String> properties) {
        if (!conceptMap.containsKey(source) || !conceptMap.containsKey(target))
            return;
        ConceptRelation cr = new ConceptRelation(false);
        cr.setEqualsString(name + ";" + source + ";" + target);
        if (conceptMap.get(source).getOutCR().contains(cr)) {
            for (ConceptRelation loopcr : conceptMap.get(source).getOutCR())
                if (loopcr.getOutConcept().getUri().equals(target))
                    cr = loopcr;
        } else
            cr = new ConceptRelation(name, conceptMap.get(source), conceptMap.get(target));
        Set<String> props = new HashSet<String>();
        for (String s : properties.keySet())
            if (!s.startsWith("~"))
                props.add(s);
        compileProperties(cr.getProperties(), props, properties);
    }

    private void compileProperties(Map<String, String> orgProperties, Set<String> props,
            Map<String, String> properties) {
        for (String s : props) {
            String opStr = properties.get("~extends." + s);
            char operation = '=';
            if (opStr != null && opStr.length() > 0)
                operation = opStr.charAt(0);
            orgProperties.put(s, compileValue(orgProperties.get(s), operation, properties.get(s)));
        }
    }

    private String compileValue(String orgValue, char operation, String newValue) {
        if (operation == '=')
            return (newValue == null ? "" : newValue);
        StringBuffer sb = new StringBuffer();
        if (orgValue != null)
            sb.append(orgValue);
        if (sb.length() > 0 && operation != '+') {
            sb.append(" ");
            sb.append(operation);
            sb.append(operation);
            sb.append(" ");
        }
        if (newValue != null)
            sb.append(newValue);
        return sb.toString();
    }

    private static Attribute makeAttribute(String name, String defaultCode, String eventCode, String type,
            boolean persistent) {
        Attribute result = new Attribute(name);
        result.setDefaultCode(defaultCode);
        result.setEventCode(eventCode);
        result.setType(type);
        try {
            if (Number.class.isAssignableFrom(Class.forName(type)))
                result.setDefaultCode("new " + type + "(" + defaultCode + ")");
            else
                result.setDefaultCode(defaultCode);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        result.setProperty("persistent", (persistent ? "true" : "false"));
        return result;
    }

    @SuppressWarnings("unused")
    private static class CRT {
        private String uid = null;
        private String name = null;
        private String code = null;
        private Map<String, CRTsocket> socketById = new HashMap<String, CRTsocket>();
        private Map<String, CRTsocket> socketByName = new HashMap<String, CRTsocket>();
        private Collection<CRTvar> variables = new LinkedList<CRTvar>();

        public String getUid() {
            return uid;
        }

        public void setUid(String uid) {
            this.uid = uid;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public Map<String, CRTsocket> getSocketById() {
            return socketById;
        }

        public CRTsocket getSocketById(String id) {
            return socketById.get(id);
        }

        public void setSocketById(Map<String, CRTsocket> socketById) {
            this.socketById = socketById;
        }

        public Map<String, CRTsocket> getSocketByName() {
            return socketByName;
        }

        public CRTsocket getSocketByName(String name) {
            return socketByName.get(name);
        }

        public void setSocketByName(Map<String, CRTsocket> socketByName) {
            this.socketByName = socketByName;
        }

        public Collection<CRTvar> getVariables() {
            return variables;
        }

        public void setVariables(Collection<CRTvar> variables) {
            this.variables = variables;
        }

        @SuppressWarnings("unchecked")
        public static CRT parse(Element crt) {
            CRT result = new CRT();
            result.setUid(crt.element("header").elementText("modeluuid"));
            result.setName(crt.element("header").elementText("title"));
            crt = crt.element("body").element("crt");
            result.setCode(crt.element("adaptationbehaviour").elementText("galcode"));
            for (Element codeElement : (List<Element>) crt.element("adaptationbehaviour").elements("code")) {
                if ("gale".equals(codeElement.attributeValue("type")))
                    result.setCode(codeElement.getText());
            }
            for (Element var : (Collection<Element>) crt.element("adaptationbehaviour").element("usermodel")
                    .elements("umvariable"))
                result.getVariables().add(CRTvar.parse(var));
            for (Element socket : (Collection<Element>) crt.element("crtsockets").elements("socket")) {
                CRTsocket s = CRTsocket.parse(socket);
                result.getSocketById().put(s.getUid(), s);
                result.getSocketByName().put(s.getName(), s);
            }
            return result;
        }
    }

    private static class CRTsocket {
        private String uid = null;
        private String name = null;

        public String getUid() {
            return uid;
        }

        public void setUid(String uid) {
            this.uid = uid;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public static CRTsocket parse(Element socket) {
            CRTsocket result = new CRTsocket();
            result.setUid(socket.elementText("uuid"));
            result.setName(socket.elementText("name"));
            return result;
        }

        public boolean equals(Object o) {
            if (o == null)
                return false;
            if (!(o instanceof CRTsocket))
                return false;
            CRTsocket s = (CRTsocket) o;
            boolean result = true;
            result &= (uid == null ? s.uid == null : uid.equals(s.uid));
            result &= (name == null ? s.name == null : name.equals(s.name));
            return result;
        }
    }

    @SuppressWarnings("unused")
    private static class CRTvar {
        private String name = null;
        private String socketName = null;
        private boolean publicVar = false;
        private boolean persistent = false;
        private String type = null;
        private String defaultVar = null;
        private String range = null;

        public String getRange() {
            return range;
        }

        public void setRange(String range) {
            this.range = range;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getSocketName() {
            return socketName;
        }

        public void setSocketName(String socketName) {
            this.socketName = socketName;
        }

        public boolean isPublicVar() {
            return publicVar;
        }

        public void setPublicVar(boolean publicVar) {
            this.publicVar = publicVar;
        }

        public boolean isPersistent() {
            return persistent;
        }

        public void setPersistent(boolean persistent) {
            this.persistent = persistent;
        }

        public String getType() {
            return type;
        }

        public String getJavaType() {
            if ("integer".equals(type))
                return "java.lang.Integer";
            if ("float".equals(type))
                return "java.lang.Float";
            if ("boolean".equals(type))
                return "java.lang.Boolean";
            return "java.lang.String";
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getDefaultVar() {
            return defaultVar;
        }

        public void setDefaultVar(String defaultVar) {
            this.defaultVar = defaultVar;
        }

        public static CRTvar parse(Element var) {
            CRTvar result = new CRTvar();
            result.setName(var.elementText("umvarname"));
            result.setDefaultVar(var.elementText("default"));
            result.setPersistent(new Boolean(var.elementText("persistent")));
            result.setPublicVar(new Boolean(var.elementText("public")));
            result.setSocketName(var.elementText("socket"));
            result.setType(var.elementText("type"));
            try {
                result.setRange(
                        var.element("range").elementText("from") + "-" + var.element("range").elementText("to"));
            } catch (Exception e) {
            }
            return result;
        }
    }

    private static class Pointer<T, U> {
        private Map<T, List<U>> data = new HashMap<T, List<U>>();
        private Map<T, U> current = new HashMap<T, U>();
        private List<T> indexList = new ArrayList<T>();

        public Pointer(Map<T, Set<U>> data) {
            for (Map.Entry<T, Set<U>> entry : data.entrySet()) {
                List<U> list = new ArrayList<U>();
                list.addAll(entry.getValue());
                this.data.put(entry.getKey(), list);
            }
            for (T t : data.keySet())
                indexList.add(t);
            for (T t : indexList)
                current.put(t, this.data.get(t).get(0));
        }

        public Map<T, U> current() {
            Map<T, U> result = new HashMap<T, U>();
            result.putAll(current);
            return result;
        }

        public void next() {
            int x = indexList.size() - 1;
            boolean done = false;
            while (x >= 0 && !done) {
                done = true;
                T t = indexList.get(x);
                int y = data.get(t).indexOf(current.get(t));
                y++;
                if (y >= data.get(t).size()) {
                    y = 0;
                    done = false;
                }
                current.put(t, data.get(t).get(y));
                x--;
            }
            if (x < 0 && !done)
                current = null;
        }

        public boolean hasPointer() {
            return (current != null);
        }
    }
}