blob: 35011df6f1cbba9c6d1abaaaccf62a3bc7b581f0 [file] [log] [blame]
/*
* Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.tools;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.BETWEEN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.DELETE_FROM;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.IN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.INNER_JOIN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.INNER_JOIN_FETCH;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.IS_EMPTY;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.IS_NOT_EMPTY;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.IS_NOT_NULL;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.IS_NULL;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.JOIN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.JOIN_FETCH;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.LEFT_JOIN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.LEFT_JOIN_FETCH;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.LEFT_OUTER_JOIN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.LEFT_OUTER_JOIN_FETCH;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.MEMBER;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.MEMBER_OF;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NOT_BETWEEN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NOT_IN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NOT_MEMBER;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NOT_MEMBER_OF;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.SELECT;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.UPDATE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
import org.eclipse.persistence.jpa.jpql.parser.IdentifierRole;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.tools.spi.IEntity;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;
import org.eclipse.persistence.jpa.jpql.tools.utility.XmlEscapeCharacterConverter;
import org.eclipse.persistence.jpa.jpql.tools.utility.iterable.SnapshotCloneIterable;
/**
* The default implementation of {@link ContentAssistProposals} which stores the valid proposals.
* <p>
* Provisional API: This interface is part of an interim API that is still under development and
* expected to change significantly before reaching stability. It is available at this early stage
* to solicit feedback from pioneering adopters on the understanding that any code that uses this
* API will almost certainly be broken (repeatedly) as the API evolves.
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public final class DefaultContentAssistProposals implements ContentAssistProposals {
/**
* The prefix that is used to filter the list of class names.
*
* @since 2.5
*/
private String classNamePrefix;
/**
* The class type helps to filter the various types of classes, i.e. anonymous, member, interface
* are never allowed.
*
* @since 2.5
*/
private ClassType classType;
/**
* TODO
*
* @since 2.5
*/
private String columnNamePrefix;
/**
* The set of possible abstract schema types.
*/
private Set<IEntity> entities;
/**
* TODO
*
* @since 2.5
*/
private Map<IType, DefaultEnumProposals> enumProposals;
/**
* This extension can be used to provide additional support to JPQL content assist that is
* outside the scope of providing proposals related to JPA metadata. It adds support for
* providing suggestions related to class names, enum constants, table names, column names.
*
* @since 2.5
*/
private ContentAssistExtension extension;
/**
* The set of possible identification variables.
*/
private Set<String> identificationVariables;
/**
* The set of possible JPQL identifiers.
*/
private Set<String> identifiers;
/**
* The {@link JPQLGrammar} that defines how the JPQL query was parsed.
*/
private JPQLGrammar jpqlGrammar;
/**
* The set of possible {@link IMapping mappings}, which can be state fields, association fields
* and/or collection fields.
*/
private Set<IMapping> mappings;
/**
* The identification variables mapped to their abstract schema types.
*/
private Map<String, IEntity> rangeIdentificationVariables;
/**
* TODO
*
* @since 2.5
*/
private String tableName;
/**
* TODO
*
* @since 2.5
*/
private String tableNamePrefix;
/**
* A JPQL identifier that is mapped to its longest counterpart.
*/
private static final Map<String, String> LONGUEST_IDENTIFIERS = buildLonguestIdentifiers();
/**
* A JPQL identifier that is mapped to the list of counterparts used to find them in the query.
*/
private static final Map<String, List<String>> ORDERED_IDENTIFIERS = buildOrderedIdentifiers();
/**
* Creates a new <code>DefaultContentAssistProposals</code>.
*
* @param jpqlGrammar The {@link JPQLGrammar} that defines how the JPQL query was parsed
* @param extension This extension can be used to provide additional support to JPQL content
* assist that is outside the scope of providing proposals related to JPA metadata. It adds
* support for providing suggestions related to class names, enum constants, table names, column
* names
*/
public DefaultContentAssistProposals(JPQLGrammar jpqlGrammar, ContentAssistExtension extension) {
super();
initialize(jpqlGrammar, extension);
}
private static Map<String, String> buildLonguestIdentifiers() {
Map<String, String> identifiers = new HashMap<String, String>();
identifiers.put(IS_EMPTY, IS_NOT_EMPTY);
identifiers.put(IS_NULL, IS_NOT_NULL);
identifiers.put(IN, NOT_IN);
identifiers.put(BETWEEN, NOT_BETWEEN);
identifiers.put(MEMBER, NOT_MEMBER_OF);
identifiers.put(MEMBER_OF, NOT_MEMBER_OF);
identifiers.put(NOT_MEMBER, NOT_MEMBER_OF);
identifiers.put(JOIN, LEFT_OUTER_JOIN_FETCH);
identifiers.put(JOIN_FETCH, LEFT_OUTER_JOIN_FETCH);
identifiers.put(LEFT_JOIN, LEFT_OUTER_JOIN_FETCH);
identifiers.put(LEFT_JOIN_FETCH, LEFT_OUTER_JOIN_FETCH);
identifiers.put(LEFT_OUTER_JOIN, LEFT_OUTER_JOIN_FETCH);
identifiers.put(INNER_JOIN, LEFT_OUTER_JOIN_FETCH);
identifiers.put(INNER_JOIN_FETCH, LEFT_OUTER_JOIN_FETCH);
identifiers.put(SELECT, DELETE_FROM);
identifiers.put(UPDATE, DELETE_FROM);
return identifiers;
}
private static Map<String, List<String>> buildOrderedIdentifiers() {
Map<String, List<String>> identifiers = new HashMap<String, List<String>>();
identifiers.put(IS_NOT_EMPTY, Collections.singletonList(IS_NOT_EMPTY));
identifiers.put(IS_NOT_NULL, Collections.singletonList(IS_NOT_NULL));
identifiers.put(NOT_IN, Collections.singletonList(NOT_IN));
identifiers.put(NOT_BETWEEN, Collections.singletonList(NOT_BETWEEN));
List<String> members = new ArrayList<String>();
members.add(MEMBER_OF);
members.add(NOT_MEMBER);
members.add(MEMBER);
identifiers.put(NOT_MEMBER_OF, members);
List<String> joins = new ArrayList<String>();
joins.add(LEFT_OUTER_JOIN);
joins.add(LEFT_JOIN_FETCH);
joins.add(LEFT_JOIN);
joins.add(INNER_JOIN_FETCH);
joins.add(INNER_JOIN);
joins.add(JOIN_FETCH);
joins.add(JOIN);
identifiers.put(LEFT_OUTER_JOIN_FETCH, joins);
List<String> clauses = new ArrayList<String>();
clauses.add(SELECT);
clauses.add(UPDATE);
identifiers.put(DELETE_FROM, clauses);
return identifiers;
}
@Override
public Iterable<IEntity> abstractSchemaTypes() {
return new SnapshotCloneIterable<IEntity>(entities);
}
/**
* Adds the given {@link IEntity} as a possible abstract schema type.
*
* @param abstractSchemaType The abstract schema type that is a valid proposal
*/
public void addEntity(IEntity abstractSchemaType) {
entities.add(abstractSchemaType);
}
/**
* Adds the constants of the given enum constant as a valid proposal.
*
* @param enumType The {@link IType} of the enum type
* @param enumConstant The enum constant to be added as a valid proposal
*
* @since 2.5
*/
public void addEnumConstant(IType enumType, String enumConstant) {
DefaultEnumProposals proposal = enumProposals.get(enumType);
if (proposal == null) {
proposal = new DefaultEnumProposals(enumType);
enumProposals.put(enumType, proposal);
}
proposal.constants.add(enumConstant);
}
/**
* Adds the given identification variable as a proposal.
*
* @param identificationVariable The identification variable that is a valid proposal
*/
public void addIdentificationVariable(String identificationVariable) {
identificationVariables.add(identificationVariable);
}
/**
* Adds the given JPQL identifier as a proposal.
*
* @param identifier The JPQL identifier that is a valid proposal
*/
public void addIdentifier(String identifier) {
identifiers.add(identifier);
}
/**
* Adds the given {@link IMapping mapping} (state field, association field or collection field)
* as a valid proposal.
*
* @param mapping The {@link IMapping} of the state field, association field or collection field
*/
public void addMapping(IMapping mapping) {
mappings.add(mapping);
}
/**
* Adds the given {@link IMapping mappings} (state fields, association fields or collection fields)
* as valid proposals.
*
* @param mappings The {@link IMapping mappings} of the state fields, association fields or
* collection fields
*/
public void addMappings(Collection<IMapping> mappings) {
this.mappings.addAll(mappings);
}
/**
* Adds the given range identification variable that is mapping the given abstract schema type.
*
* @param identificationVariable The range identification variable mapping the abstract schema name
* @param entity The abstract type name that identifies the type of the variable
*/
public void addRangeIdentificationVariable(String identificationVariable, IEntity entity) {
rangeIdentificationVariables.put(identificationVariable, entity);
}
@Override
public Result buildEscapedQuery(String jpqlQuery, String proposal, int position, boolean insert) {
Result result = buildQuery(jpqlQuery, proposal, position, insert);
// Escape the JPQL query and adjust the position accordingly
int[] positions = { result.position };
result.jpqlQuery = ExpressionTools.escape(result.jpqlQuery, positions);
result.position = positions[0];
return result;
}
/**
* Calculates the start and end position for correctly inserting the proposal into the query.
*
* @param wordParser This parser can be used to retrieve words from the cursor position
* @param proposal The proposal to be inserted into the query
* @param insert Flag that determines if the proposal is simply inserted or it should also replace
* the partial word following the position of the cursor
* @return The start and end positions
*/
public int[] buildPositions(WordParser wordParser, String proposal, boolean insert) {
int index;
String wordsToReplace = null;
// Special case for arithmetic symbol (<, <=, =, <>, >, >=, +, -, *, /)
if (wordParser.isArithmeticSymbol(proposal.charAt(0))) {
int startIndex = wordParser.position();
int endIndex = startIndex;
char character;
// Now look for the start index
do {
character = wordParser.character(--startIndex);
}
while (wordParser.isArithmeticSymbol(character));
// Now look for the end index
do {
character = wordParser.character(endIndex++);
}
while (wordParser.isArithmeticSymbol(character));
// Increment the start index because the loop always decrements it by one before stopping
// and decrement the end position because it's always incremented by one before stopping
index = ++startIndex;
wordsToReplace = wordParser.substring(index, --endIndex);
}
else {
// Always try to find the start position using the longest JPQL identifier
// if the proposal has more than one word
wordsToReplace = longuestIdentifier(proposal);
index = startPosition(wordParser, wordsToReplace);
// Now check with the other possibilities
if ((index == -1) && ORDERED_IDENTIFIERS.containsKey(wordsToReplace)) {
for (String identifier : ORDERED_IDENTIFIERS.get(wordsToReplace)) {
wordsToReplace = identifier;
index = startPosition(wordParser, wordsToReplace);
if (index > -1) {
break;
}
}
}
}
// Now calculate the start position
int startPosition;
if (index > -1) {
startPosition = index;
}
else {
// Check for path expression (which uses a dot) and update the wordToReplace
// and startPosition accordingly
String partialWord = wordParser.partialWord();
String entireWord = wordParser.entireWord();
int dotIndex = -1;
if (isMappingName(proposal) ||
isEnumConstant(proposal) ||
isColumnName(proposal)) {
dotIndex = partialWord.lastIndexOf('.');
}
if (dotIndex > 0) {
wordsToReplace = entireWord.substring(dotIndex + 1);
startPosition = wordParser.position() - partialWord.length() + dotIndex + 1;
}
else if (insert) {
wordsToReplace = wordParser.word();
startPosition = wordParser.position() - partialWord.length();
}
else {
wordsToReplace = entireWord;
startPosition = wordParser.position() - partialWord.length();
}
}
// Now calculate the end position
int length;
// Only the partial word to the left of the cursor will be replaced and
// the partial word to the right of the cursor will stay
if (insert) {
String partialWord = wordParser.substring(startPosition, wordParser.position());
length = partialWord.length();
}
else if (proposal != wordsToReplace) {
length = wordsToReplace.length();
}
else {
index = 0;
for (int charIndex = 0, wordLength = wordParser.length(), count = wordsToReplace.length();
startPosition + index < wordLength && charIndex < count;
index++, charIndex++) {
// Find the end position by matching the proposal with the content of the query by
// scanning every character starting at the start position, the end position will be
// the position of the first character that is different or at the end of the query
if (wordParser.character(startPosition + charIndex) !=
wordsToReplace.charAt(charIndex)) {
break;
}
}
length = index;
}
return new int[] { startPosition, startPosition + length };
}
@Override
public Result buildQuery(String jpqlQuery, String proposal, int position, boolean insert) {
// Nothing to replace
if (ExpressionTools.stringIsEmpty(proposal)) {
return new Result(jpqlQuery, position);
}
WordParser wordParser = new WordParser(jpqlQuery);
wordParser.setPosition(position);
// Calculate the start and end positions
int[] positions = buildPositions(wordParser, proposal, insert);
// Create the new query
StringBuilder sb = new StringBuilder(jpqlQuery);
sb.replace(positions[0], positions[1], proposal);
// Return the result
return new Result(sb.toString(), positions[0] + proposal.length());
}
@Override
public ResultQuery buildXmlQuery(String jpqlQuery, String proposal, int position, boolean insert) {
// Nothing to replace
if (ExpressionTools.stringIsEmpty(proposal)) {
return new Result(jpqlQuery, position);
}
int[] positions = { position };
// First convert the escape characters into their unicode characters
String query = XmlEscapeCharacterConverter.unescape(jpqlQuery, positions);
// Calculate the start and end positions
WordParser wordParser = new WordParser(query);
wordParser.setPosition(positions[0]);
int[] proposalPositions = buildPositions(wordParser, proposal, insert);
// Escape the proposal
proposal = XmlEscapeCharacterConverter.escape(proposal, new int[1]);
// Adjust the positions so it's in the original JPQL query, which may contain escaped characters
XmlEscapeCharacterConverter.reposition(jpqlQuery, proposalPositions);
// Create the new JPQL query
StringBuilder sb = new StringBuilder(jpqlQuery);
sb.replace(proposalPositions[0], proposalPositions[1], proposal);
// Return the result
return new Result(sb.toString(), proposalPositions[0] + proposal.length());
}
@Override
public Iterable<String> classNames() {
if (classNamePrefix == null) {
return Collections.emptyList();
}
return extension.classNames(classNamePrefix, classType);
}
@Override
public Iterable<String> columnNames() {
if (tableName == null) {
return Collections.emptyList();
}
return extension.columnNames(tableName, columnNamePrefix);
}
@Override
public Iterable<EnumProposals> enumConstant() {
return new SnapshotCloneIterable<EnumProposals>(enumProposals.values());
}
@Override
public IEntity getAbstractSchemaType(String identificationVariable) {
return rangeIdentificationVariables.get(identificationVariable);
}
/**
* Returns the prefix that will be used to filter the list of possible class names.
*
* @return The prefix that is used to filter the list of class names or <code>null</code> if it
* was not set for the cursor position within the JPQL query
* @since 2.5
*/
public String getClassNamePrefix() {
return classNamePrefix;
}
@Override
public ClassType getClassType() {
return classType;
}
/**
* Returns the prefix that will be used by {@link ContentAssistExtension} to filter the column
* names if the table name is not <code>null</code>.
*
* @return The prefix that is used to filter the list of columns names, which is <code>null</code>
* if it has not been set along with the table name
* @since 2.5
*/
public String getColumnNamePrefix() {
return columnNamePrefix;
}
/**
* Returns the {@link JPQLGrammar} that defines how the JPQL query was parsed.
*
* @return The {@link JPQLGrammar} that was used to parse this {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression}
*/
public JPQLGrammar getGrammar() {
return jpqlGrammar;
}
@Override
public IdentifierRole getIdentifierRole(String identifier) {
return jpqlGrammar.getExpressionRegistry().getIdentifierRole(identifier);
}
/**
* Returns the table name that will be used by {@link ContentAssistExtension} to retrieve the
* column names.
*
* @return The name of the table for which its column names should be retrieve as possible proposals
* @since 2.5
*/
public String getTableName() {
return tableName;
}
/**
* Returns the prefix that will be used to filter the list of possible table names.
*
* @return The prefix that is used to filter the list of table names or <code>null</code> if it
* was not set for the cursor position within the JPQL query
* @since 2.5
*/
public String getTableNamePrefix() {
return tableNamePrefix;
}
@Override
public boolean hasProposals() {
return !mappings.isEmpty() ||
!entities.isEmpty() ||
!identifiers.isEmpty() ||
!enumProposals.isEmpty() ||
!identificationVariables.isEmpty() ||
classNames().iterator().hasNext() ||
tableNames().iterator().hasNext() ||
columnNames().iterator().hasNext() ||
!rangeIdentificationVariables.isEmpty();
}
@Override
public Iterable<String> identificationVariables() {
List<String> variables = new ArrayList<String>(identificationVariables.size() + rangeIdentificationVariables.size());
variables.addAll(identificationVariables);
variables.addAll(rangeIdentificationVariables.keySet());
return new SnapshotCloneIterable<String>(variables);
}
@Override
public Iterable<String> identifiers() {
return new SnapshotCloneIterable<String>(identifiers);
}
protected void initialize(JPQLGrammar jpqlGrammar, ContentAssistExtension extension) {
this.extension = extension;
this.jpqlGrammar = jpqlGrammar;
this.mappings = new HashSet<IMapping>();
this.identifiers = new HashSet<String>();
this.entities = new HashSet<IEntity>();
this.identificationVariables = new HashSet<String>();
this.rangeIdentificationVariables = new HashMap<String, IEntity>();
this.enumProposals = new HashMap<IType, DefaultEnumProposals>();
}
/**
* Determines whether the given proposal is a column name (which should be unqualified).
*
* @param proposal The proposal that is being inserted into the JPQL query
* @return <code>true</code> if the given proposal is a column name; <code>false</code> otherwise
* @since 2.5
*/
public boolean isColumnName(String proposal) {
for (String columName : columnNames()) {
if (columName.equals(proposal)) {
return true;
}
}
return false;
}
/**
* Determines whether the given proposal is an enum constant name (which should be unqualified).
*
* @param proposal The proposal that is being inserted into the JPQL query
* @return <code>true</code> if the given proposal is a unqualified enum constant name;
* <code>false</code> otherwise
* @since 2.5
*/
public boolean isEnumConstant(String proposal) {
for (EnumProposals enumProposal : enumProposals.values()) {
for (String enumConstant : enumProposal.enumConstants()) {
if (enumConstant.equals(proposal)) {
return true;
}
}
}
return false;
}
/**
* Determines whether the given proposal is a mapping name.
*
* @param proposal The proposal that is being inserted into the JPQL query
* @return <code>true</code> if the given proposal is a mapping name; <code>false</code> otherwise
* @since 2.5
*/
public boolean isMappingName(String proposal) {
for (IMapping mapping : mappings) {
if (mapping.getName().equals(proposal)) {
return true;
}
}
return false;
}
/**
* Returns the longest possible JPQL identifier that is related to the given proposal if the
* proposal is a JPQL identifier and contains multiple words. For instance, the longest form of
* <code>JOIN</code> or <code>JOIN FETCH</code> is <code>LEFT OUTER JOIN FETCH</code>.
*
* @param proposal The proposal to retrieve its longest form if one is associated with it
* @return Either the given proposal if it's not a JPQL identifier or it does not have a longer
* form or the longest version of the JPQL identifier
*/
public String longuestIdentifier(String proposal) {
return LONGUEST_IDENTIFIERS.containsKey(proposal) ? LONGUEST_IDENTIFIERS.get(proposal) : proposal;
}
@Override
public Iterable<IMapping> mappings() {
return new SnapshotCloneIterable<IMapping>(mappings);
}
/**
* Removes the given JPQL identifier.
*
* @param identifier The identifier that was added but actually needs to be removed
*/
protected void removeIdentifier(String identifier) {
identifiers.remove(identifier);
}
/**
* Adds the given prefix that will be used to filter the list of possible class names.
*
* @param prefix The prefix that is used to filter the list of class names
* @param classType Determines how to filter the various types of classes
* @since 2.5
*/
public void setClassNamePrefix(String prefix, ClassType classType) {
this.classNamePrefix = prefix;
this.classType = classType;
}
/**
* Sets the table name and a prefix that will be used to filter the names of the table's columns.
*
* @param tableName The name of the table for which its column names should be retrieve as
* possible proposals
* @param prefix The prefix that is used to filter the list of columns names, which is never
* <code>null</code> but can be an empty string
* @since 2.5
*/
public void setTableName(String tableName, String prefix) {
this.tableName = tableName;
this.columnNamePrefix = prefix;
}
/**
* Adds the given prefix that will be used to filter the list of possible columns names.
*
* @param tableNamePrefix The prefix that is used to filter the list of columns names
* @since 2.5
*/
public void setTableNamePrefix(String tableNamePrefix) {
this.tableNamePrefix = tableNamePrefix;
}
public int startPosition(WordParser wordParser, String proposal) {
int index = wordParser.position();
int maxMoveLength = proposal.length();
while (index >= 0 && maxMoveLength > 0) {
if (wordParser.startsWithIgnoreCase(proposal, index)) {
return index;
}
index--;
maxMoveLength--;
}
return -1;
}
@Override
public Iterable<String> tableNames() {
if (tableNamePrefix == null) {
return Collections.emptyList();
}
return extension.tableNames(tableNamePrefix);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
// JPQL identifiers
if (!identifiers.isEmpty()) {
sb.append(identifiers);
}
// Entity names
if (!entities.isEmpty()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(entities);
}
// Identification variables
if (!identificationVariables.isEmpty()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(identificationVariables);
}
// Range identification variables
if (!rangeIdentificationVariables.isEmpty()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(rangeIdentificationVariables);
}
// Mappings
if (!mappings.isEmpty()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(mappings);
}
// Class names
for (String className : classNames()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(className);
}
// Table names
for (String tableName : tableNames()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(tableName);
}
// Column names
for (String columnName : columnNames()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(columnName);
}
// Enum constant names
for (EnumProposals enumProposals : enumConstant()) {
for (String enumConstant : enumProposals.enumConstants()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(enumConstant);
}
}
// No proposals
if (sb.length() == 0) {
sb.append("<No Default Proposals>");
}
return sb.toString();
}
private static final class DefaultEnumProposals implements EnumProposals {
private Set<String> constants;
private IType enumType;
DefaultEnumProposals(IType enumType) {
super();
this.enumType = enumType;
this.constants = new HashSet<String>();
}
@Override
public Iterable<String> enumConstants() {
return new SnapshotCloneIterable<String>(constants);
}
@Override
public IType enumType() {
return enumType;
}
}
/**
* This contains the result of inserting a proposal into a JPQL query at a given position.
*
* @see ContentAssistProposals#buildEscapedQuery(String, String, int, boolean)
* @see ContentAssistProposals#buildQuery(String, String, int, boolean)
*/
private static final class Result implements ResultQuery {
/**
* The new JPQL query after insertion of the proposal.
*/
private String jpqlQuery;
/**
* The position of the cursor within the new query.
*/
private int position;
/**
* Creates a new <code>Result</code>.
*
* @param jpqlQuery The new JPQL query after insertion of the proposal
* @param position The position of the cursor within the new query
*/
public Result(String jpqlQuery, int position) {
super();
this.jpqlQuery = jpqlQuery;
this.position = position;
}
@Override
public int getPosition() {
return position;
}
@Override
public String getQuery() {
return jpqlQuery;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Query=[").append(jpqlQuery);
sb.append("], position=").append(position);
return sb.toString();
}
}
}