blob: 6ba4165dbf3899e6e74e48cf6e97e125d49b8869 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.directory.studio.ldapbrowser.common.filtereditor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.api.ldap.model.schema.MatchingRule;
import org.apache.directory.api.ldap.model.schema.ObjectClass;
import org.apache.directory.studio.ldapbrowser.common.BrowserCommonActivator;
import org.apache.directory.studio.ldapbrowser.common.BrowserCommonConstants;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilter;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilterExtensibleComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.LdapFilterItemComponent;
import org.apache.directory.studio.ldapbrowser.core.model.filter.parser.LdapFilterParser;
import org.apache.directory.studio.ldapbrowser.core.model.filter.parser.LdapFilterToken;
import org.apache.directory.studio.ldapbrowser.core.model.schema.Schema;
import org.apache.directory.studio.ldapbrowser.core.model.schema.SchemaUtils;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateCompletionProcessor;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.swt.graphics.Image;
/**
* The FilterContentAssistProcessor computes the content proposals for the filter editor.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class FilterContentAssistProcessor extends TemplateCompletionProcessor implements IContentAssistProcessor,
IContentProposalProvider
{
private static final Comparator<String> NAME_AND_OID_COMPARATOR = new Comparator<String>()
{
public int compare( String s1, String s2 )
{
if ( s1.matches( "[0-9\\.]+" ) && !s2.matches( "[0-9\\.]+" ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
return 1;
}
else if ( !s1.matches( "[0-9\\.]+" ) && s2.matches( "[0-9\\.]+" ) ) //$NON-NLS-1$ //$NON-NLS-2$
{
return -1;
}
else
{
return s1.compareToIgnoreCase( s2 );
}
}
};
/** The parser. */
private LdapFilterParser parser;
/** The source viewer, may be null. */
private ISourceViewer sourceViewer;
/** The auto activation characters. */
private char[] autoActivationCharacters;
/** The schema, used to retrieve attributeType and objectClass information. */
private Schema schema;
/** The possible attribute types. */
private Map<String, AttributeType> possibleAttributeTypes;
/** The possible filter types. */
private Map<String, String> possibleFilterTypes;
/** The possible object classes. */
private Map<String, ObjectClass> possibleObjectClasses;
/** The possible matching rules. */
private Map<String, MatchingRule> possibleMatchingRules;
/**
* Creates a new instance of FilterContentAssistProcessor.
*
* @param parser the parser
*/
public FilterContentAssistProcessor( LdapFilterParser parser )
{
this( null, parser );
}
/**
* Creates a new instance of FilterContentAssistProcessor.
*
* @param sourceViewer the source viewer
* @param parser the parser
*/
public FilterContentAssistProcessor( ISourceViewer sourceViewer, LdapFilterParser parser )
{
this.parser = parser;
this.sourceViewer = sourceViewer;
this.autoActivationCharacters = new char[7 + 10 + 26 + 26];
this.autoActivationCharacters[0] = '(';
this.autoActivationCharacters[1] = ')';
this.autoActivationCharacters[2] = '&';
this.autoActivationCharacters[3] = '|';
this.autoActivationCharacters[4] = '!';
this.autoActivationCharacters[5] = ':';
this.autoActivationCharacters[6] = '.';
int i = 7;
for ( char c = 'a'; c <= 'z'; c++, i++ )
{
this.autoActivationCharacters[i] = c;
}
for ( char c = 'A'; c <= 'Z'; c++, i++ )
{
this.autoActivationCharacters[i] = c;
}
for ( char c = '0'; c <= '9'; c++, i++ )
{
this.autoActivationCharacters[i] = c;
}
}
/**
* Sets the schema, used to retrieve attributeType and objectClass information.
*
* @param schema the schema
*/
public void setSchema( Schema schema )
{
this.schema = schema;
possibleAttributeTypes = new TreeMap<String, AttributeType>( NAME_AND_OID_COMPARATOR );
possibleFilterTypes = new LinkedHashMap<String, String>();
possibleObjectClasses = new TreeMap<String, ObjectClass>( NAME_AND_OID_COMPARATOR );
possibleMatchingRules = new TreeMap<String, MatchingRule>( NAME_AND_OID_COMPARATOR );
if ( schema != null )
{
Collection<AttributeType> attributeTypeDescriptions = schema.getAttributeTypeDescriptions();
for ( AttributeType atd : attributeTypeDescriptions )
{
possibleAttributeTypes.put( atd.getOid(), atd );
for ( String atdName : atd.getNames() )
{
possibleAttributeTypes.put( atdName, atd );
}
}
possibleFilterTypes.put( "=", Messages.getString( "FilterContentAssistProcessor.Equals" ) ); //$NON-NLS-1$ //$NON-NLS-2$
possibleFilterTypes.put( "=*", Messages.getString( "FilterContentAssistProcessor.Present" ) ); //$NON-NLS-1$ //$NON-NLS-2$
possibleFilterTypes.put( "<=", Messages.getString( "FilterContentAssistProcessor.LessThanOrEquals" ) ); //$NON-NLS-1$ //$NON-NLS-2$
possibleFilterTypes.put( ">=", Messages.getString( "FilterContentAssistProcessor.GreaterThanOrEquals" ) ); //$NON-NLS-1$ //$NON-NLS-2$
possibleFilterTypes.put( "~=", Messages.getString( "FilterContentAssistProcessor.Approximately" ) ); //$NON-NLS-1$ //$NON-NLS-2$
Collection<ObjectClass> ocds = schema.getObjectClassDescriptions();
for ( ObjectClass ocd : ocds )
{
possibleObjectClasses.put( ocd.getOid(), ocd );
for ( String name : ocd.getNames() )
{
possibleObjectClasses.put( name, ocd );
}
}
Collection<MatchingRule> matchingRuleDescriptions = schema.getMatchingRuleDescriptions();
for ( MatchingRule description : matchingRuleDescriptions )
{
possibleMatchingRules.put( description.getOid(), description );
for ( String name : description.getNames() )
{
possibleMatchingRules.put( name, description );
}
}
}
}
/**
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getCompletionProposalAutoActivationCharacters()
*/
public char[] getCompletionProposalAutoActivationCharacters()
{
return autoActivationCharacters;
}
/**
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
*/
public ICompletionProposal[] computeCompletionProposals( ITextViewer viewer, int offset )
{
return computeCompletionProposals( offset );
}
/**
* @see org.eclipse.jface.fieldassist.IContentProposalProvider#getProposals(java.lang.String, int)
*/
public IContentProposal[] getProposals( final String contents, final int position )
{
parser.parse( contents );
ICompletionProposal[] oldProposals = computeCompletionProposals( position );
IContentProposal[] proposals = new IContentProposal[oldProposals.length];
for ( int i = 0; i < oldProposals.length; i++ )
{
final ICompletionProposal oldProposal = oldProposals[i];
final Document document = new Document( contents );
oldProposal.apply( document );
proposals[i] = new IContentProposal()
{
public String getContent()
{
return document.get();
}
public int getCursorPosition()
{
return oldProposal.getSelection( document ).x;
}
public String getDescription()
{
return oldProposal.getAdditionalProposalInfo();
}
public String getLabel()
{
return oldProposal.getDisplayString();
}
public String toString()
{
return getContent();
}
};
}
return proposals;
}
/**
* Computes completion proposals.
*
* @param offset the offset
*
* @return the matching completion proposals
*/
private ICompletionProposal[] computeCompletionProposals( int offset )
{
String[] possibleObjectClasses = schema == null ? new String[0] : SchemaUtils.getNamesAsArray( schema
.getObjectClassDescriptions() );
Arrays.sort( possibleObjectClasses );
List<ICompletionProposal> proposalList = new ArrayList<ICompletionProposal>();
LdapFilter filter = parser.getModel().getFilter( offset );
if ( filter != null && offset > 0 )
{
// case 0: open curly started, show templates and all attribute types
if ( filter.getStartToken() != null && filter.getFilterComponent() == null )
{
if ( sourceViewer != null )
{
ICompletionProposal[] templateProposals = super.computeCompletionProposals( sourceViewer, offset );
if ( templateProposals != null )
{
proposalList.addAll( Arrays.asList( templateProposals ) );
}
}
addPossibleAttributeTypes( proposalList, "", offset ); //$NON-NLS-1$
}
// case A: simple filter
if ( filter.getFilterComponent() instanceof LdapFilterItemComponent )
{
LdapFilterItemComponent fc = ( LdapFilterItemComponent ) filter.getFilterComponent();
// case A1: editing attribute type: show matching attribute types
if ( fc.getStartToken().getOffset() <= offset
&& offset <= fc.getStartToken().getOffset() + fc.getStartToken().getLength() )
{
addPossibleAttributeTypes( proposalList, fc.getAttributeToken().getValue(), fc.getAttributeToken()
.getOffset() );
}
String attributeType = null;
if ( schema != null && schema.hasAttributeTypeDescription( fc.getAttributeToken().getValue() ) )
{
attributeType = fc.getAttributeToken().getValue();
}
// case A2: after attribte type: show possible filter types and extensible match options
if ( attributeType != null )
{
if ( ( fc.getAttributeToken().getOffset() <= offset || fc.getFilterToken() != null )
&& offset <= fc.getAttributeToken().getOffset() + fc.getAttributeToken().getLength()
+ ( fc.getFilterToken() != null ? fc.getFilterToken().getLength() : 0 ) )
{
//String attributeType = fc.getAttributeToken().getValue();
String filterType = fc.getFilterToken() != null ? fc.getFilterToken().getValue() : ""; //$NON-NLS-1$
int filterTypeOffset = fc.getAttributeToken().getOffset() + fc.getAttributeToken().getLength();
addPossibleFilterTypes( proposalList, attributeType, filterType, filterTypeOffset );
}
}
// case A3: editing objectClass attribute: show matching object classes
if ( attributeType != null && SchemaConstants.OBJECT_CLASS_AT.equalsIgnoreCase( attributeType ) )
{
if ( ( fc.getValueToken() != null && fc.getValueToken().getOffset() <= offset || fc
.getFilterToken() != null )
&& offset <= fc.getAttributeToken().getOffset() + fc.getAttributeToken().getLength()
+ ( fc.getFilterToken() != null ? fc.getFilterToken().getLength() : 0 )
+ ( fc.getValueToken() != null ? fc.getValueToken().getLength() : 0 ) )
{
addPossibleObjectClasses( proposalList, fc.getValueToken() == null ? "" : fc.getValueToken() //$NON-NLS-1$
.getValue(), fc.getValueToken() == null ? offset : fc.getValueToken().getOffset() );
}
}
}
// case B: extensible filter
if ( filter.getFilterComponent() instanceof LdapFilterExtensibleComponent )
{
LdapFilterExtensibleComponent fc = ( LdapFilterExtensibleComponent ) filter.getFilterComponent();
// case B1: editing extensible attribute type: show matching attribute types
if ( fc.getAttributeToken() != null && fc.getAttributeToken().getOffset() <= offset
&& offset <= fc.getAttributeToken().getOffset() + fc.getAttributeToken().getLength() )
{
addPossibleAttributeTypes( proposalList, fc.getAttributeToken().getValue(), fc.getAttributeToken()
.getOffset() );
}
// case B2: editing dn
if ( fc.getDnAttrToken() != null && fc.getDnAttrToken().getOffset() <= offset
&& offset <= fc.getDnAttrToken().getOffset() + fc.getDnAttrToken().getLength() )
{
addDnAttr( proposalList, fc.getDnAttrToken().getValue(), fc.getDnAttrToken().getOffset() );
}
// case B3: editing matching rule
if ( fc.getMatchingRuleColonToken() != null
&& fc.getMatchingRuleToken() == null
&& fc.getMatchingRuleColonToken().getOffset() <= offset
&& offset <= fc.getMatchingRuleColonToken().getOffset()
+ fc.getMatchingRuleColonToken().getLength() )
{
if ( fc.getDnAttrColonToken() == null )
{
addDnAttr( proposalList, "", offset ); //$NON-NLS-1$
}
addPossibleMatchingRules( proposalList, "", offset, fc.getEqualsColonToken(), fc.getEqualsToken() ); //$NON-NLS-1$
}
if ( fc.getMatchingRuleToken() != null && fc.getMatchingRuleToken().getOffset() <= offset
&& offset <= fc.getMatchingRuleToken().getOffset() + fc.getMatchingRuleToken().getLength() )
{
if ( fc.getDnAttrColonToken() == null )
{
addDnAttr( proposalList, fc.getMatchingRuleToken().getValue(), fc.getMatchingRuleToken()
.getOffset() );
}
String matchingRuleValue = fc.getMatchingRuleToken().getValue();
addPossibleMatchingRules( proposalList, matchingRuleValue, fc.getMatchingRuleToken().getOffset(),
fc.getEqualsColonToken(), fc.getEqualsToken() );
}
}
}
return proposalList.toArray( new ICompletionProposal[0] );
}
/**
* Adds the possible attribute types to the proposal list.
*
* @param proposalList the proposal list
* @param attributeType the current attribute type
* @param offset the offset
*/
private void addPossibleAttributeTypes( List<ICompletionProposal> proposalList, String attributeType, int offset )
{
if ( schema != null )
{
for ( String possibleAttributeType : possibleAttributeTypes.keySet() )
{
AttributeType description = possibleAttributeTypes.get( possibleAttributeType );
if ( possibleAttributeType.toUpperCase().startsWith( attributeType.toUpperCase() ) )
{
String replacementString = possibleAttributeType;
String displayString = possibleAttributeType;
if ( displayString.equals( description.getOid() ) )
{
displayString += " (" + SchemaUtils.toString( description ) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
displayString += " (" + description.getOid() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
String info = SchemaUtils.getLdifLine( description );
ICompletionProposal proposal = new CompletionProposal( replacementString, offset, attributeType
.length(), replacementString.length(), getAttributeTypeImage(), displayString, null, info );
proposalList.add( proposal );
}
}
}
}
/**
* Adds the possible attribute types to the proposal list.
*
* @param proposalList the proposal list
* @param attributeType the current attribute type
* @param offset the offset
*/
private void addPossibleFilterTypes( List<ICompletionProposal> proposalList, String attributeType,
String filterType, int offset )
{
if ( schema != null )
{
Map<String, String> copy = new LinkedHashMap<String, String>( possibleFilterTypes );
if ( SchemaUtils.getEqualityMatchingRuleNameOrNumericOidTransitive( schema
.getAttributeTypeDescription( attributeType ), schema ) == null )
{
copy.remove( "=" ); //$NON-NLS-1$
copy.remove( "~=" ); //$NON-NLS-1$
}
if ( SchemaUtils.getOrderingMatchingRuleNameOrNumericOidTransitive( schema
.getAttributeTypeDescription( attributeType ), schema ) == null )
{
copy.remove( "<=" ); //$NON-NLS-1$
copy.remove( ">=" ); //$NON-NLS-1$
}
for ( String possibleFilterType : copy.keySet() )
{
String replacementString = possibleFilterType;
String displayString = copy.get( possibleFilterType );
ICompletionProposal proposal = new CompletionProposal( replacementString, offset, filterType.length(),
possibleFilterType.length(), getFilterTypeImage(), displayString, null, null );
proposalList.add( proposal );
}
}
}
/**
* Adds the possible object classes to the proposal list.
*
* @param proposalList the proposal list
* @param objectClasses the object class
* @param offset the offset
*/
private void addPossibleObjectClasses( List<ICompletionProposal> proposalList, String objectClass, int offset )
{
if ( schema != null )
{
for ( String possibleObjectClass : possibleObjectClasses.keySet() )
{
ObjectClass description = possibleObjectClasses.get( possibleObjectClass );
if ( possibleObjectClass.toUpperCase().startsWith( objectClass.toUpperCase() ) )
{
String replacementString = possibleObjectClass;
String displayString = possibleObjectClass;
if ( displayString.equals( description.getOid() ) )
{
displayString += " (" + SchemaUtils.toString( description ) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
displayString += " (" + description.getOid() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
ICompletionProposal proposal = new CompletionProposal( replacementString, offset, objectClass
.length(), replacementString.length(), getObjectClassImage(), displayString, null, SchemaUtils
.getLdifLine( schema.getObjectClassDescription( possibleObjectClass ) ) );
proposalList.add( proposal );
}
}
}
}
/**
* Adds the possible matching rules (that fits to the given attribute type) to the proposal list.
*
* @param proposalList the proposal list
* @param matchingRule the matching rule
* @param offset the offset
*/
private void addPossibleMatchingRules( List<ICompletionProposal> proposalList, String matchingRule, int offset,
LdapFilterToken equalsColonToken, LdapFilterToken equalsToken )
{
if ( schema != null )
{
for ( String possibleMatchingRule : possibleMatchingRules.keySet() )
{
if ( possibleMatchingRule.toUpperCase().startsWith( matchingRule.toUpperCase() ) )
{
MatchingRule description = schema.getMatchingRuleDescription( possibleMatchingRule );
String replacementString = possibleMatchingRule;
if ( equalsColonToken == null )
{
replacementString += ":"; //$NON-NLS-1$
}
if ( equalsToken == null )
{
replacementString += "="; //$NON-NLS-1$
}
String displayString = possibleMatchingRule;
if ( displayString.equals( description.getOid() ) )
{
displayString += " (" + SchemaUtils.toString( description ) + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
displayString += " (" + description.getOid() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
String info = SchemaUtils.getLdifLine( description );
ICompletionProposal proposal = new CompletionProposal( replacementString, offset, matchingRule
.length(), replacementString.length(), getMatchingRuleImage(), displayString, null, info );
proposalList.add( proposal );
}
}
}
}
/**
* Adds the dn: proposal to the proposal list.
*
* @param proposalList the proposal list
* @param dnAttr the dn attr
* @param offset the offset
*/
private void addDnAttr( List<ICompletionProposal> proposalList, String dnAttr, int offset )
{
if ( "dn".toUpperCase().startsWith( dnAttr.toUpperCase() ) ) //$NON-NLS-1$
{
String replacementString = "dn:"; //$NON-NLS-1$
String displayString = "dn: ()"; //$NON-NLS-1$
ICompletionProposal proposal = new CompletionProposal( replacementString, offset, dnAttr.length(),
replacementString.length(), null, displayString, null, null );
proposalList.add( proposal );
}
}
/**
* Gets the attribute type image.
*
* @return the attribute type image
*/
private Image getAttributeTypeImage()
{
return BrowserCommonActivator.getDefault().getImage( BrowserCommonConstants.IMG_ATD );
}
/**
* Gets the filter type image.
*
* @return the filter type image
*/
private Image getFilterTypeImage()
{
return BrowserCommonActivator.getDefault().getImage( BrowserCommonConstants.IMG_FILTER_EDITOR );
}
/**
* Gets the object class image.
*
* @return the object class image
*/
private Image getObjectClassImage()
{
return BrowserCommonActivator.getDefault().getImage( BrowserCommonConstants.IMG_OCD );
}
/**
* Gets the matching rule image.
*
* @return the matching rule image
*/
private Image getMatchingRuleImage()
{
return BrowserCommonActivator.getDefault().getImage( BrowserCommonConstants.IMG_MRD );
}
/**
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getTemplates(java.lang.String)
*/
protected Template[] getTemplates( String contextTypeId )
{
Template[] templates = BrowserCommonActivator.getDefault().getFilterTemplateStore().getTemplates(
BrowserCommonConstants.FILTER_TEMPLATE_ID );
return templates;
}
/**
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getContextType(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
*/
protected TemplateContextType getContextType( ITextViewer viewer, IRegion region )
{
TemplateContextType contextType = BrowserCommonActivator.getDefault().getFilterTemplateContextTypeRegistry()
.getContextType( BrowserCommonConstants.FILTER_TEMPLATE_ID );
return contextType;
}
/**
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getImage(org.eclipse.jface.text.templates.Template)
*/
protected Image getImage( Template template )
{
return BrowserCommonActivator.getDefault().getImage( BrowserCommonConstants.IMG_TEMPLATE );
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer, int)
*/
public IContextInformation[] computeContextInformation( ITextViewer viewer, int documentOffset )
{
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
*/
public char[] getContextInformationAutoActivationCharacters()
{
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
*/
public String getErrorMessage()
{
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
public IContextInformationValidator getContextInformationValidator()
{
return null;
}
}