org.semanticweb.owlapi.sparql.ui.AutoCompleter.java Source code

Java tutorial

Introduction

Here is the source code for org.semanticweb.owlapi.sparql.ui.AutoCompleter.java

Source

/*
 * This file is part of the OWL API.
 *
 * The contents of this file are subject to the LGPL License, Version 3.0.
 *
 * Copyright (C) 2011, The University of Manchester
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 *
 * Alternatively, the contents of this file may be used under the terms of the Apache License, Version 2.0
 * in which case, the provisions of the Apache License Version 2.0 are applicable instead of those above.
 *
 * Copyright 2011, The University of Manchester
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.semanticweb.owlapi.sparql.ui;

import com.google.common.collect.Sets;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.sparql.api.UntypedVariable;
import org.semanticweb.owlapi.sparql.api.Variable;
import org.semanticweb.owlapi.sparql.builtin.BuiltInCall;
import org.semanticweb.owlapi.sparql.parser.SPARQLParserImpl;
import org.semanticweb.owlapi.sparql.parser.tokenizer.*;
import org.semanticweb.owlapi.sparql.parser.tokenizer.impl.SPARQLTokenizerJavaCCImpl;
import org.semanticweb.owlapi.vocab.Namespaces;
import org.semanticweb.owlapi.vocab.OWL2Datatype;
import org.semanticweb.owlapi.vocab.OWLRDFVocabulary;

import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.io.StringReader;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Author: Matthew Horridge<br> Stanford University<br> Bio-Medical Informatics Research Group<br> Date: 27/03/2012
 */
public class AutoCompleter {

    public static final int DEFAULT_MAX_ENTRIES = 100;
    public static final int POPUP_WIDTH = 350;
    public static final int POPUP_HEIGHT = 300;
    public static final int TYPE_LIMIT = 1000;
    private JTextComponent textComponent;
    private Set<String> wordDelimeters;
    private AutoCompleterMatcher matcher;
    private JList popupList;
    private JWindow popupWindow;
    private String lastTextUpdate = "*";
    private int maxEntries = DEFAULT_MAX_ENTRIES;
    private KeyListener keyListener = new KeyAdapter() {
        public void keyPressed(KeyEvent e) {
            processKeyPressed(e);
        }

        public void keyReleased(KeyEvent e) {

            if (e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN) {
                if (popupWindow.isVisible() && !lastTextUpdate.equals(textComponent.getText())) {
                    lastTextUpdate = textComponent.getText();
                    updatePopup(getMatches());
                }
            }
        }
    };
    private ComponentAdapter componentListener = new ComponentAdapter() {
        public void componentHidden(ComponentEvent event) {
            hidePopup();
        }

        public void componentResized(ComponentEvent event) {
            hidePopup();
        }

        public void componentMoved(ComponentEvent event) {
            hidePopup();
        }
    };
    private HierarchyListener hierarchyListener = new HierarchyListener() {
        /**
         * Called when the hierarchy has been changed. To discern the actual
         * type of change, call <code>HierarchyEvent.getChangeFlags()</code>.
         * @see java.awt.event.HierarchyEvent#getChangeFlags()
         */
        public void hierarchyChanged(HierarchyEvent e) {
            if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0) {
                createPopupWindow();
                Container frame = textComponent.getTopLevelAncestor();
                if (frame != null) {
                    frame.addComponentListener(componentListener);
                }
            }
        }
    };
    private MouseListener mouseListener = new MouseAdapter() {
        public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() == 2) {
                completeWithPopupSelection();
            }
        }
    };
    private FocusListener focusListener = new FocusAdapter() {
        public void focusLost(FocusEvent event) {
            hidePopup();
        }
    };
    private OWLOntologyProvider ontologyProvider;

    public AutoCompleter(OWLOntologyProvider ontologyProvider, JTextComponent tc) {
        this.ontologyProvider = ontologyProvider;
        this.textComponent = tc;

        wordDelimeters = new HashSet<String>();
        wordDelimeters.add(" ");
        wordDelimeters.add("\n");
        wordDelimeters.add("\t");
        wordDelimeters.add("\r");
        wordDelimeters.add("[");
        wordDelimeters.add("]");
        wordDelimeters.add("{");
        wordDelimeters.add("}");
        wordDelimeters.add("(");
        wordDelimeters.add(")");
        wordDelimeters.add(",");
        wordDelimeters.add("^");

        //        matcher = new AutoCompleterMatcherImpl(ontology);

        popupList = new JList();
        popupList.setAutoscrolls(true);
        //        popupList.setCellRenderer(owlEditorKit.getWorkspace().createOWLCellRenderer());
        popupList.addMouseListener(mouseListener);
        popupList.setRequestFocusEnabled(false);

        textComponent.addKeyListener(keyListener);

        textComponent.addHierarchyListener(hierarchyListener);

        // moving or resizing the text component or dialog closes the popup
        textComponent.addComponentListener(componentListener);

        // switching focus to another component closes the popup
        textComponent.addFocusListener(focusListener);

        createPopupWindow();
    }

    private static Set<? extends OWLEntity> getSignatureOfType(EntityType<?> et, OWLOntology o) {
        Set<OWLEntity> result = new HashSet<>();
        if (et == EntityType.CLASS) {
            OWLDataFactory df = o.getOWLOntologyManager().getOWLDataFactory();
            return Sets.union(o.getClassesInSignature(true), Collections.singleton(df.getOWLThing()));
        } else if (et == EntityType.OBJECT_PROPERTY) {
            return o.getObjectPropertiesInSignature(true);
        } else if (et == EntityType.DATA_PROPERTY) {
            return o.getDataPropertiesInSignature(true);
        } else if (et == EntityType.ANNOTATION_PROPERTY) {
            Set<OWLAnnotationProperty> builtIns = getBuiltInAnnotationProperties(o);
            return Sets.union(o.getAnnotationPropertiesInSignature(), builtIns);
        } else if (et == EntityType.NAMED_INDIVIDUAL) {
            return o.getIndividualsInSignature();
        } else {
            return o.getDatatypesInSignature();
        }
    }

    private static Set<OWLAnnotationProperty> getBuiltInAnnotationProperties(OWLOntology o) {
        Set<OWLAnnotationProperty> builtIns = new HashSet<>();
        for (IRI iri : OWLRDFVocabulary.BUILT_IN_ANNOTATION_PROPERTY_IRIS) {
            builtIns.add(o.getOWLOntologyManager().getOWLDataFactory().getOWLAnnotationProperty(iri));
        }
        return builtIns;
    }

    private static String renderEntity(PrefixManager pm, OWLEntity entity) {
        String pn = pm.getPrefixIRI(entity.getIRI());
        if (pn != null) {
            return pn;
        } else {
            return entity.getIRI().toQuotedString();
        }
    }

    public void cancel() {
        hidePopup();
    }

    private void processKeyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SPACE && e.isControlDown()) {
            // Show popup
            performAutoCompletion();
        }
        //        else if (e.getKeyCode() == KeyEvent.VK_TAB) {
        //            e.consume();
        //            performAutoCompletion();
        //        }
        else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            if (popupWindow.isVisible()) {
                // Hide popup
                e.consume();
                hidePopup();
            }
        } else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            if (popupWindow.isVisible()) {
                // Complete
                e.consume();
                completeWithPopupSelection();
            }
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            if (popupWindow.isVisible()) {
                e.consume();
                incrementSelection();
            }
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            if (popupWindow.isVisible()) {
                e.consume();
                decrementSelection();
            }
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            hidePopup();
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            hidePopup();
        }
    }

    private void completeWithPopupSelection() {
        if (popupWindow.isVisible()) {
            Object selObject = popupList.getSelectedValue();
            if (selObject != null) {
                if (selObject instanceof OWLEntity) {
                    insertWord(getInsertText(selObject));
                } else {
                    insertWord(getInsertText(selObject));
                }
                hidePopup();
            }
        }
    }

    private java.util.List getMatches() {
        // We need to determine if the matches should be classes, individuals etc.

        try {
            int wordIndex = getWordIndex();
            String expression = textComponent.getDocument().getText(0, wordIndex);
            // Add a bit to the end to force a parse error
            expression += "";
            OWLOntology ontology = ontologyProvider.getOntology();
            SPARQLTokenizerJavaCCImpl tokenizer = new SPARQLTokenizerJavaCCImpl(ontology,
                    new StringReader(expression));
            SPARQLParserImpl sparqlParser = new SPARQLParserImpl(tokenizer);
            try {
                sparqlParser.parseQuery();
            } catch (SPARQLParseException e) {
                String word = getWordToComplete();
                Set matches = getMatchesFromParseException(ontology, tokenizer, e);
                List<String> kwMatches = new ArrayList<String>(matches.size() + 10);
                for (Object s : matches) {
                    String objectRendering = s.toString();
                    if (isMatchWith(word, objectRendering)) {
                        kwMatches.add(objectRendering);
                    }
                }
                return kwMatches;
            }
        } catch (BadLocationException e) {
        }
        return Collections.EMPTY_LIST;
    }

    private boolean isMatchWith(String word, String objectRendering) {
        return objectRendering.toLowerCase().startsWith(word.toLowerCase());
    }

    private Set getMatchesFromParseException(OWLOntology ontology, SPARQLTokenizerJavaCCImpl tokenizer,
            SPARQLParseException e) {
        Set<String> matches = new TreeSet<>();

        e.getExpectedVocabulary().stream().map(OWLRDFVocabulary::getIRI)
                .map(iri -> tokenizer.getPrefixManager().getPrefixIRI(iri)).forEach(matches::add);

        e.getExpectedEntityTypes().stream().forEach(et -> {
            ontology.getImportsClosure().stream().map(o -> getSignatureOfType(et, o)).flatMap(Collection::stream)
                    .limit(TYPE_LIMIT).map(entity -> renderEntity(tokenizer.getPrefixManager(), entity))
                    .forEach(matches::add);
        });

        e.getExpectedTerminals().stream().map(SPARQLTerminal::getImage).forEach(matches::add);

        if (e.getExpectedTokenTypes().contains(BuiltInCallTokenType.get())) {
            Stream.of(BuiltInCall.values()).filter(BuiltInCall::isSupported).map(BuiltInCall::name)
                    .forEach(matches::add);
        }

        boolean prologueIRIExpected = e.getExpectedTokenTypes().contains(PrologueDeclarationIRITokenType.get());
        boolean prologuePrefixNameExpected = e.getExpectedTokenTypes().contains(PrefixNameTokenType.get());

        if (prologueIRIExpected || prologuePrefixNameExpected) {
            Map<String, String> prefixNamesToPrefixes = new HashMap<>();
            Set<String> prefixes = new HashSet<>();
            prefixes.add(Namespaces.OWL.getPrefixIRI());
            prefixes.add(Namespaces.RDF.getPrefixIRI());
            prefixes.add(Namespaces.RDFS.getPrefixIRI());
            prefixes.add(Namespaces.XSD.getPrefixIRI());
            prefixes.add(Namespaces.XML.getPrefixIRI());

            for (OWLOntology ont : ontology.getImportsClosure()) {
                for (OWLEntity entity : ont.getSignature()) {
                    IRI iri = entity.getIRI();
                    String prefix = null;
                    String prefixName = null;
                    int lastStop = iri.length();
                    for (int i = iri.length() - 1; i > 0; i--) {
                        char ch = iri.charAt(i);
                        if (ch == '#' || ch == '/') {
                            if (prefix == null) {
                                CharSequence seq = iri.subSequence(0, i + 1);
                                prefix = seq.toString();
                                prefixes.add(prefix);
                                lastStop = i;
                            } else if (prefixName == null) {
                                CharSequence seq = iri.subSequence(i + 1, lastStop);
                                String seqString = seq.toString();
                                if (!OWL2Datatype.XSD_DECIMAL.getPattern().matcher(seqString).matches()) {
                                    prefixName = seqString;
                                    if (prefixName.endsWith(".owl")) {
                                        prefixName = prefixName.substring(0, prefixName.length() - 4);
                                    }
                                }
                                lastStop = i;
                            }
                        }
                    }
                    if (!prefixes.contains(prefix)) {
                        prefixNamesToPrefixes.put(prefixName, prefix);
                    }
                }
            }

            for (String prefix : prefixes) {
                for (Namespaces ns : Namespaces.values()) {
                    if (ns.getPrefixIRI().equals(prefix)) {
                        prefixNamesToPrefixes.put(ns.getPrefixName(), ns.getPrefixIRI());
                    }
                }
            }

            if (prologueIRIExpected) {
                prefixes.stream().map(prefix -> "<" + prefix + ">").forEach(matches::add);
            }

            if (prologuePrefixNameExpected) {
                prefixNamesToPrefixes.keySet().stream().map(prefixName -> prefixName + ":").forEach(matches::add);
            }
        }

        for (TokenType tokenType : e.getExpectedTokenTypes()) {
            if (tokenType instanceof VariableTokenType) {
                for (Variable variable : e.getParsedVariables()) {
                    if (variable instanceof UntypedVariable) {
                        matches.add(variable.getVariableNamePrefix().getPrefix() + variable.getName());
                    }
                }
            }
            //            else if(tokenType instanceof DeclaredVariableTokenType) {
            //                DeclaredVariableTokenType declaredVariableTokenType = (DeclaredVariableTokenType) tokenType;
            //                for(Variable variable : e.getParsedVariables()) {
            //                    if(variable.getType() == declaredVariableTokenType.getPrimitiveType()) {
            //                        matches.add(variable.getVariableNamePrefix().getPrefix() + variable.getName());
            //                    }
            //                }
            //            }
        }
        return matches;
    }

    private void createPopupWindow() {
        JScrollPane sp = new JScrollPane(popupList);
        popupWindow = new JWindow((Window) SwingUtilities.getAncestorOfClass(Window.class, textComponent));
        //        popupWindow.setAlwaysOnTop(true); // this doesn't appear to work with certain Windows/java combinations
        popupWindow.getContentPane().setLayout(new BorderLayout());
        popupWindow.getContentPane().add(sp, BorderLayout.CENTER);
        popupWindow.setFocusableWindowState(false);
    }

    private void performAutoCompletion() {
        java.util.List matches = getMatches();
        if (matches.size() == 1) {
            // Don't show popup
            insertWord(getInsertText(matches.iterator().next()));
        } else if (matches.size() > 1) {
            // Show popup
            lastTextUpdate = textComponent.getText();
            showPopup();
            updatePopup(matches);
        }
    }

    private void insertWord(String word) {
        try {
            // remove any currently selected text - this is the default behaviour
            // of the editor when typing manually
            int selStart = textComponent.getSelectionStart();
            int selEnd = textComponent.getSelectionEnd();
            int selLen = selEnd - selStart;
            if (selLen > 0) {
                textComponent.getDocument().remove(selStart, selLen);
            }

            int index = getWordIndex();
            int caretIndex = textComponent.getCaretPosition();
            if (caretIndex > 0 && caretIndex > index) {
                textComponent.getDocument().remove(index, caretIndex - index);
            }
            textComponent.getDocument().insertString(index, word, null);
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    }

    private void showPopup() {
        if (popupWindow == null) {
            createPopupWindow();
        }
        if (!popupWindow.isVisible()) {
            popupWindow.setSize(POPUP_WIDTH, POPUP_HEIGHT);
            try {
                int wordIndex = getWordIndex();
                Point p = new Point(0, 0); // default for when the doc is empty
                if (wordIndex > 0) {
                    p = textComponent.modelToView(wordIndex).getLocation();
                }
                SwingUtilities.convertPointToScreen(p, textComponent);
                p.y = p.y + textComponent.getFontMetrics(textComponent.getFont()).getHeight();
                popupWindow.setLocation(p);
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
            popupWindow.setVisible(true);
        }
    }

    private void hidePopup() {
        popupWindow.setVisible(false);
        popupList.setListData(new Object[0]);
    }

    private void updatePopup(java.util.List matches) {
        int count = matches.size();
        if (count > maxEntries) {
            count = maxEntries;
        }
        if (!matches.isEmpty()) {
            popupList.setListData(matches.subList(0, count).toArray());
        } else {
            popupList.setListData(matches.toArray());
        }
        popupList.setSelectedIndex(0);

        popupWindow.setSize(POPUP_WIDTH, POPUP_HEIGHT);

    }

    private void incrementSelection() {
        if (popupList.getModel().getSize() > 0) {
            int selIndex = popupList.getSelectedIndex();
            selIndex++;
            if (selIndex > popupList.getModel().getSize() - 1) {
                selIndex = 0;
            }
            popupList.setSelectedIndex(selIndex);
            popupList.scrollRectToVisible(popupList.getCellBounds(selIndex, selIndex));
        }
    }

    private void decrementSelection() {
        if (popupList.getModel().getSize() > 0) {
            int selIndex = popupList.getSelectedIndex();
            selIndex--;
            if (selIndex < 0) {
                selIndex = popupList.getModel().getSize() - 1;
            }
            popupList.setSelectedIndex(selIndex);
            popupList.scrollRectToVisible(popupList.getCellBounds(selIndex, selIndex));
        }
    }

    private int getWordIndex() {
        int index = getEscapedWordIndex();
        if (index == -1) {
            index = getUnbrokenWordIndex();
        }
        return Math.max(0, index);
    }

    // determines if we are currently inside an escaped name (if there are an uneven number of escape characters)
    private int getEscapedWordIndex() {
        try {
            int caretPos = Math.max(0, getEffectiveCaretPosition() - 1);
            String expression = textComponent.getDocument().getText(0, caretPos);
            int escapeEnd = -1;
            do {
                int escapeStart = expression.indexOf("'", escapeEnd + 1);
                if (escapeStart != -1) {
                    escapeEnd = expression.indexOf("'", escapeStart + 1);
                    if (escapeEnd == -1) {
                        return escapeStart;
                    }
                } else {
                    return -1;
                }
            } while (true);
        } catch (BadLocationException e) {
        }
        return -1;
    }

    private int getUnbrokenWordIndex() {
        try {
            int caretPos = Math.max(0, getEffectiveCaretPosition() - 1);
            if (caretPos > 0) {
                for (int index = caretPos; index > -1; index--) {
                    if (wordDelimeters.contains(textComponent.getDocument().getText(index, 1))) {
                        return index + 1;
                    }
                    if (index == 0) {
                        return 0;
                    }
                }
            }
        } catch (BadLocationException e) {
        }
        return -1;
    }

    private String getInsertText(Object o) {
        //        if (o instanceof OWLObject) {
        //            OWLModelManager mngr = owlEditorKit.getModelManager();
        //            return mngr.getRendering((OWLObject) o);
        //        }
        //        else {
        return o.toString();
        //        }
    }

    private String getWordToComplete() {
        try {
            int index = getWordIndex();
            int caretIndex = getEffectiveCaretPosition();
            return textComponent.getDocument().getText(index, caretIndex - index);
        } catch (BadLocationException e) {
            return "";
        }
    }

    // the caret pos should be read as the start of the selection if there is one
    private int getEffectiveCaretPosition() {
        int startSel = textComponent.getSelectionStart();
        if (startSel >= 0) {
            return startSel;
        }
        return textComponent.getCaretPosition();
    }

    public void uninstall() {
        hidePopup();
        textComponent.removeKeyListener(keyListener);
        textComponent.removeComponentListener(componentListener);
        textComponent.removeFocusListener(focusListener);
        textComponent.removeHierarchyListener(hierarchyListener);
    }
}