Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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 com.haulmont.cuba.gui.components.autocomplete.impl; import com.haulmont.cuba.core.sys.jpql.*; import com.haulmont.cuba.core.sys.jpql.antlr2.JPA2RecognitionException; import com.haulmont.cuba.core.sys.jpql.model.Attribute; import com.haulmont.cuba.core.sys.jpql.model.JpqlEntityModel; import com.haulmont.cuba.core.sys.jpql.model.NoJpqlEntityModel; import com.haulmont.cuba.core.sys.jpql.pointer.CollectionPointer; import com.haulmont.cuba.core.sys.jpql.pointer.EntityPointer; import com.haulmont.cuba.core.sys.jpql.pointer.NoPointer; import com.haulmont.cuba.core.sys.jpql.pointer.Pointer; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.CommonTree; import org.apache.commons.lang.StringUtils; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; public class HintProvider { private DomainModel model; private static final char CARET_POSITION_SYMBOL = '~'; public static final Pattern COLLECTION_MEMBER_PATTERN = Pattern .compile(".*\\sin\\s*[(]\\s*[a-zA-Z0-9]+[.][a-zA-Z0-9.]*$"); public static final Pattern JOIN_PATTERN = Pattern.compile(".*\\sjoin\\s*[a-zA-Z0-9]+[.][a-zA-Z0-9.]*$"); public static final String[] ARITHMETIC_OPERATIONS = { "+", "-", "*", "/" }; public HintProvider(DomainModel model) { if (model == null) throw new NullPointerException("No model passed"); this.model = model; } /** * Returns word in query denoting entity or field parameter user have requested hint for * @param queryString query string * @param caretPosition caret position * @return matched word or empty string */ public static String getLastWord(String queryString, int caretPosition) { if (caretPosition < 0) return ""; if (Character.isSpaceChar(queryString.charAt(caretPosition))) { return ""; } String[] words = queryString.substring(0, caretPosition + 1).split("\\s"); String result = words[words.length - 1]; if (StringUtils.isBlank(result)) { return result; } int leftBracketsIdx = result.lastIndexOf('('); if (leftBracketsIdx >= 0 && leftBracketsIdx < result.length()) { result = result.substring(leftBracketsIdx + 1); } result = getLastWordWithArithmeticOperation(result); return result; } private static String getLastWordWithArithmeticOperation(String word) { if (!word.contains("'")) { int operationIdx = StringUtils.lastIndexOfAny(word, ARITHMETIC_OPERATIONS); if (operationIdx >= 0 && operationIdx < word.length()) { return word.substring(operationIdx + 1); } } return word; } public HintResponse requestHint(String queryStringWithCaret) throws RecognitionException { int caretPosition = queryStringWithCaret.indexOf(CARET_POSITION_SYMBOL); if (caretPosition == -1) throw new IllegalStateException("No caret position found"); if (caretPosition == 0) throw new IllegalStateException("Caret at the beginning of the query"); caretPosition -= 1; String queryString = queryStringWithCaret.substring(0, caretPosition + 1) + queryStringWithCaret.substring(caretPosition + 2); MacroProcessor macroProcessor = new MacroProcessor(); HintRequest hintRequest = macroProcessor.inlineFake(queryString, caretPosition); AliasRemover aliasRemover = new AliasRemover(); hintRequest = aliasRemover.replaceAliases(hintRequest); return requestHint(hintRequest); } public HintResponse requestHint(HintRequest hintRequest) throws RecognitionException { AliasRemover aliasRemover = new AliasRemover(); hintRequest = aliasRemover.replaceAliases(hintRequest); String input = hintRequest.getQuery(); int cursorPos = hintRequest.getPosition(); Set<InferredType> expectedTypes = hintRequest.getExpectedTypes() == null ? EnumSet.of(InferredType.Any) : hintRequest.getExpectedTypes(); String lastWord = getLastWord(input, cursorPos); expectedTypes = narrowExpectedTypes(input, cursorPos, expectedTypes); return (!lastWord.contains(".")) ? hintEntityName(lastWord) : hintFieldName(lastWord, input, cursorPos, expectedTypes); } private HintResponse hintFieldName(String lastWord, String input, int caretPosition, Set<InferredType> expectedTypes) throws RecognitionException { QueryTreeAnalyzer queryAnalyzer = new QueryTreeAnalyzer(); try { queryAnalyzer.prepare(model, input, false); } catch (RecognitionException | JPA2RecognitionException e) { List<String> errorMessages = new ArrayList<>(); errorMessages.add(e.getMessage()); return new HintResponse("Query error", errorMessages); } List<ErrorRec> errorRecs = queryAnalyzer.getInvalidIdVarNodes(); QueryVariableContext root = queryAnalyzer.getRootQueryVariableContext(); if (root == null) { List<String> errorMessages = prepareErrorMessages(errorRecs); errorMessages.add(0, "Query variable context is null"); return new HintResponse("Query error", errorMessages); } QueryVariableContext queryVC = root.getContextByCaretPosition(caretPosition); EntityPath path = EntityPath.parseEntityPath(lastWord); Pointer pointer = path.resolvePointer(model, queryVC); if (pointer instanceof NoPointer) { List<String> errorMessages = prepareErrorMessages(errorRecs); errorMessages.add(0, "Cannot parse [" + lastWord + "]"); return new HintResponse("Query error", errorMessages); } if (pointer instanceof CollectionPointer) { List<String> errorMessages = prepareErrorMessages(errorRecs); errorMessages.add(0, "Cannot get attribute of collection [" + lastWord + "]"); return new HintResponse("Query error", errorMessages); } if (!(pointer instanceof EntityPointer)) { List<String> errorMessages = prepareErrorMessages(errorRecs); return new HintResponse("Query error", errorMessages); } List<Option> options = new ArrayList<>(); JpqlEntityModel targetEntity = ((EntityPointer) pointer).getEntity(); if (targetEntity instanceof NoJpqlEntityModel) return new HintResponse(options, path.lastEntityFieldPattern); List<Attribute> attributes = targetEntity.findAttributesStartingWith(path.lastEntityFieldPattern, expectedTypes); for (Attribute attribute : attributes) { options.add(new Option(attribute.getName(), attribute.getUserFriendlyName())); } return new HintResponse(options, path.lastEntityFieldPattern); } private List<String> prepareErrorMessages(List<ErrorRec> errorRecs) { List<String> errorMessages = new ArrayList<>(); for (ErrorRec errorRec : errorRecs) { CommonTree errorNode = errorRec.node; StringBuilder b = new StringBuilder(); for (Object child : errorNode.getChildren()) { CommonTree childNode = (CommonTree) child; b.append(childNode.getText()); } String errorMessage = "Error near: \"" + b + "\""; errorMessages.add(errorMessage); } return errorMessages; } private HintResponse hintEntityName(String lastWord) { List<JpqlEntityModel> matchingEntities = model.findEntitiesStartingWith(lastWord); List<Option> options = new ArrayList<>(); for (JpqlEntityModel entity : matchingEntities) { options.add(new Option(entity.getName(), entity.getUserFriendlyName())); } return new HintResponse(options, lastWord); } public static Set<InferredType> narrowExpectedTypes(String input, int cursorPos, Set<InferredType> expectedTypes) { if (StringUtils.isEmpty(input)) { return expectedTypes; } if (cursorPos >= 0 && cursorPos < input.length() && input.charAt(cursorPos) == ' ') { return expectedTypes; } String matchingInput = input.substring(0, cursorPos + 1); Matcher matcher = COLLECTION_MEMBER_PATTERN.matcher(matchingInput); if (matcher.matches()) { return EnumSet.of(InferredType.Collection, InferredType.Entity); } matcher = JOIN_PATTERN.matcher(matchingInput); if (matcher.matches()) { return EnumSet.of(InferredType.Collection, InferredType.Entity); } return expectedTypes; } }