/*
* 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.openldap.config.editor.wrappers;
import java.util.ArrayList;
import java.util.List;
import org.apache.directory.api.util.Strings;
import org.apache.directory.studio.common.ui.widgets.OrderedElement;
import org.apache.directory.studio.openldap.common.ui.model.DnSpecStyleEnum;
import org.apache.directory.studio.openldap.common.ui.model.DnSpecTypeEnum;
import org.apache.directory.studio.openldap.common.ui.model.LimitSelectorEnum;
/**
* This class wraps the olcLimits parameter :
* <pre>
* olcLimits ::= selector limit limit-e
* selector ::= '*' | 'anonymous' | 'users' | dnspec '=' pattern | group '=' pattern
* dnspec ::= 'dn' type-e style-e
* type-e ::= '.self' | '.this' | e
* style-e ::= '.exact' | '.base' | '.one' | '.onelevel' | '.sub' | '.subtree' | '.children' | '.regex' | '.anonymous' | e
* pattern ::= '*' | '.*' | '"' REGEXP '"'
* group ::= 'group' group-oc
* group-oc ::= '/' OBJECT_CLASS group-at | e
* group-at ::= '/' ATTRIBUTE_TYPE | e
* limit ::= 'time' time-limit | 'size' size-limit
* time-limit ::= '.soft=' limit-value| '.hard=' time-hard | '=' limit-value
* time-hard ::= limit-value | 'soft'
* size-limit ::= '.soft=' limit-value | '.hard=' size-hard | '.unchecked=' | '=' limit-value
* size-hard ::= limit-value | 'soft' | 'disable'
* limit-value ::= INT | 'unlimited'
* </pre>
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class LimitsWrapper implements Cloneable, Comparable<LimitsWrapper>, OrderedElement
{
/** Prefix, as the Limits are ordered */
private int prefix;
/** The selector */
private LimitSelectorEnum selector;
/** The pattern if the selector is a dnspec or a group */
private String selectorPattern;
/** The type if the selector is a DnSpec */
private DnSpecTypeEnum dnSpecType;
/** The style if the selector is a DnSpec */
private DnSpecStyleEnum dnSpecStyle;
/** The group ObjectClass */
private String objectClass;
/** The group AttributeType */
private String attributeType;
/** The list of limits, as Strings */
private List<LimitWrapper> limits = new ArrayList<LimitWrapper>();
/** A flag to tell if the limits is valid or not */
private boolean isValid = true;
/** A flag used when the limit is invalid */
private static final int ERROR = -1;
/** A flag used when the parsing is completed */
private static final int EOL = Integer.MIN_VALUE;
/**
* Create a LimitsrWrapper instance
*/
public LimitsWrapper()
{
}
/**
* Create a LimitsrWrapper instance from a String
*
* @param limitsStr The String that contain the value
*/
public LimitsWrapper( String limitsStr )
{
if ( limitsStr != null )
{
// use a lowercase version of the string
String lowerCaseLimits = limitsStr.toLowerCase();
int pos = 0;
// It's ordered : process the prefix
if ( Strings.isCharASCII( lowerCaseLimits, pos, '{' ) )
{
pos++;
prefix = 0;
while ( pos < lowerCaseLimits.length() )
{
char c = lowerCaseLimits.charAt( pos );
if ( ( c >= '0' ) && ( c <= '9' ) )
{
prefix = prefix * 10 + ( c - '0' );
pos++;
}
else if ( c == '}' )
{
pos++;
break;
}
else
{
isValid = false;
break;
}
}
}
if ( isValid )
{
lowerCaseLimits = lowerCaseLimits.substring( pos );
// Process the selector
if ( lowerCaseLimits.startsWith( LimitSelectorEnum.ANY.getName() ) )
{
selector = LimitSelectorEnum.ANY;
pos += LimitSelectorEnum.ANY.getName().length();
}
if ( lowerCaseLimits.startsWith( LimitSelectorEnum.ANONYMOUS.getName() ) )
{
selector = LimitSelectorEnum.ANONYMOUS;
pos += LimitSelectorEnum.ANONYMOUS.getName().length();
}
else if ( lowerCaseLimits.startsWith( LimitSelectorEnum.USERS.getName() ) )
{
selector = LimitSelectorEnum.USERS;
pos += LimitSelectorEnum.USERS.getName().length();
}
else if ( lowerCaseLimits.startsWith( LimitSelectorEnum.DNSPEC.getName() ) )
{
selector = LimitSelectorEnum.DNSPEC;
pos += LimitSelectorEnum.DNSPEC.getName().length();
// parse the type
pos = parseDnSpec( lowerCaseLimits, pos );
if ( pos == ERROR )
{
isValid = false;
}
}
else if ( lowerCaseLimits.startsWith( LimitSelectorEnum.GROUP.getName() ) )
{
selector = LimitSelectorEnum.GROUP;
pos += LimitSelectorEnum.GROUP.getName().length();
pos = parseGroup( lowerCaseLimits, pos );
if ( pos == ERROR )
{
// This is an error
isValid = false;
}
}
}
// Process the limits, only if the selector was valid
if ( isValid )
{
boolean noLimit = true;
while ( pos >= 0 )
{
pos = parseLimit( lowerCaseLimits, pos );
if ( noLimit )
{
if ( pos == EOL )
{
// We must have at least one limit
{
isValid = false;
break;
}
}
else
{
noLimit = false;
}
}
if ( pos == ERROR )
{
isValid = false;
break;
}
}
}
}
}
/**
* Parse the DNSpec part :
* <pre>
* dnspec ::= 'dn' type-e style-e '=' pattern
* type-e ::= '.self' | '.this' | e
* style-e ::= '.exact' | '.base' | '.one' | '.onelevel' | '.sub' | '.subtree' | '.children' | '.regex' | '.anonymous' | e
* </pre>
*/
private int parseDnSpec( String str, int pos )
{
// The type
if ( str.startsWith( ".self", pos ) )
{
dnSpecType = DnSpecTypeEnum.SELF;
pos += 5;
}
else if ( str.startsWith( ".this", pos ) )
{
dnSpecType = DnSpecTypeEnum.THIS;
pos += 5;
}
// The style
if ( str.startsWith( ".exact", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.EXACT;
pos += 6;
}
else if ( str.startsWith( ".base", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.BASE;
pos += 5;
}
else if ( str.startsWith( ".onelevel", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.ONE_LEVEL;
pos += 9;
}
else if ( str.startsWith( ".one", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.ONE_LEVEL;
pos += 4;
}
else if ( str.startsWith( ".subtree", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.SUBTREE;
pos += 8;
}
else if ( str.startsWith( ".sub", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.SUBTREE;
pos += 4;
}
else if ( str.startsWith( ".children", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.CHILDREN;
pos += 9;
}
else if ( str.startsWith( ".regex", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.REGEXP;
pos += 6;
}
else if ( str.startsWith( ".anonymous", pos ) )
{
dnSpecStyle = DnSpecStyleEnum.ANONYMOUS;
pos += 10;
}
// The pattern
return parsePattern( str, pos );
}
/**
* Parse the group part :
* <pre>
* group ::= 'group' group-oc '=' pattern
* group-oc ::= '/' OBJECT_CLASS group-at | e
* group-at ::= '/' ATTRIBUTE_TYPE | e
* </pre>
*/
private int parseGroup( String str, int pos )
{
// Check if we have an ObjectClass
if ( Strings.isCharASCII( str, pos, '/' ) )
{
int i = pos + 1;
for ( ; i < str.length(); i++ )
{
char c = str.charAt( i );
if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) ||
( ( c >= '0' ) && ( c <= '9' ) ) || ( c == '.' ) || ( c == '-' ) || ( c == '_' ) )
{
continue;
}
}
if ( i > pos + 1 )
{
// An ObjectClass
objectClass = str.substring( pos + 1, i );
}
pos = i;
}
// Check if we have an AttributeType
if ( Strings.isCharASCII( str, pos, '/' ) )
{
int i = pos + 1;
for ( ; i < str.length(); i++ )
{
char c = str.charAt( i );
if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) ||
( ( c >= '0' ) && ( c <= '9' ) ) || ( c == '.' ) || ( c == '-' ) || ( c == '_' ) )
{
continue;
}
}
if ( i > pos + 1 )
{
// An AttributeType
attributeType = str.substring( pos + 1, i );
}
pos = i;
}
// The pattern
return parsePattern( str, pos );
}
/**
* Search for a pattern
*/
private int parsePattern( String str, int pos )
{
if ( !Strings.isCharASCII( str, pos, '=' ) )
{
return ERROR;
}
pos++;
if ( !Strings.isCharASCII( str, pos, '"' ) )
{
return ERROR;
}
pos++;
boolean escapeSeen = false;
for ( int i = pos; i < str.length(); i++ )
{
if ( str.charAt( i ) == '\\' )
{
escapeSeen = !escapeSeen;
}
else
{
if ( str.charAt( i ) == '"' )
{
if ( escapeSeen )
{
escapeSeen = false;
}
else
{
// This is the end of the patter
selectorPattern = str.substring( pos, i );
return i + 1;
}
}
else
{
escapeSeen = false;
}
}
}
// The final '"' has not been found, this is an error.
return ERROR;
}
/**
* Parses the limit.
* <pre>
* limit ::= 'time' time-limit | 'size' size-limit
* time-limit ::= '.soft=' limit-value| '.hard=' time-hard | '=' limit-value
* time-hard ::= limit-value | 'soft'
* size-limit ::= '.soft=' limit-value | '.hard=' size-hard | '.unchecked=' | '=' limit-value
* size-hard ::= limit-value | 'soft' | 'disable'
* limit-value ::= INT | 'unlimited' | 'none'
* </pre>
*/
private int parseLimit( String str, int pos )
{
// Remove spaces
while ( Strings.isCharASCII( str, pos, ' ' ) )
{
pos++;
}
String limitStr = str.substring( pos );
if ( Strings.isEmpty( limitStr ) )
{
return EOL;
}
int i = 0;
if ( limitStr.startsWith( "time" ) )
{
// fetch the time limit (everything that goes up to a space or the end of the string
for ( ; i < limitStr.length(); i++ )
{
if ( limitStr.charAt( i ) == ' ' )
{
break;
}
}
if ( i == 0 )
{
return ERROR;
}
TimeLimitWrapper timeLimitWrapper = new TimeLimitWrapper( limitStr.substring( 0, i ) );
if ( timeLimitWrapper.isValid() )
{
limits.add( timeLimitWrapper );
return pos + i;
}
else
{
return ERROR;
}
}
else if ( limitStr.startsWith( "size" ) )
{
// fetch the size limit (everything that goes up to a space or the end of the string
for ( ;i < limitStr.length(); i++ )
{
if ( limitStr.charAt( i ) == ' ' )
{
break;
}
}
if ( i == 0 )
{
return ERROR;
}
SizeLimitWrapper sizeLimitWrapper = new SizeLimitWrapper( limitStr.substring( 0, i ) );
if ( sizeLimitWrapper.isValid() )
{
limits.add( sizeLimitWrapper );
return pos + i;
}
else
{
return ERROR;
}
}
else
{
return ERROR;
}
}
/**
* Sets a new prefix
*
* @param prefix the prefix to set
*/
public void setPrefix( int prefix )
{
this.prefix = prefix;
}
/**
* @return the prefix
*/
public int getPrefix()
{
return prefix;
}
/**
* {@inheritDoc}
*/
public void decrementPrefix()
{
prefix--;
}
/**
* {@inheritDoc}
*/
public void incrementPrefix()
{
prefix++;
}
/**
* @return the selector
*/
public LimitSelectorEnum getSelector()
{
return selector;
}
/**
* @param selector the selector to set
*/
public void setSelector( LimitSelectorEnum selector )
{
this.selector = selector;
}
/**
* @return the selectorPattern
*/
public String getSelectorPattern()
{
return selectorPattern;
}
/**
* @param selectorPattern the selectorPattern to set
*/
public void setSelectorPattern( String selectorPattern )
{
this.selectorPattern = selectorPattern;
}
/**
* @return the dnSpecType
*/
public DnSpecTypeEnum getDnSpecType()
{
return dnSpecType;
}
/**
* @param dnSpecType the dnSpecType to set
*/
public void setDnSpecType( DnSpecTypeEnum dnSpecType )
{
this.dnSpecType = dnSpecType;
}
/**
* @return the dnSpecStyle
*/
public DnSpecStyleEnum getDnSpecStyle()
{
return dnSpecStyle;
}
/**
* @param dnSpecStyle the dnSpecStyle to set
*/
public void setDnSpecStyle( DnSpecStyleEnum dnSpecStyle )
{
this.dnSpecStyle = dnSpecStyle;
}
/**
* @return the objectClass
*/
public String getObjectClass()
{
return objectClass;
}
/**
* @param objectClass the objectClass to set
*/
public void setObjectClass( String objectClass )
{
this.objectClass = objectClass;
}
/**
* @return the attributeType
*/
public String getAttributeType()
{
return attributeType;
}
/**
* @param attributeType the attributeType to set
*/
public void setAttributeType( String attributeType )
{
this.attributeType = attributeType;
}
/**
* @return the limits
*/
public List<LimitWrapper> getLimits()
{
return limits;
}
/**
* @param limits the limits to set
*/
public void setLimits( List<LimitWrapper> limits )
{
this.limits = limits;
}
/**
* Tells if the Limits element is valid or not
* @return true if the values are correct, false otherwise
*/
public boolean isValid()
{
return isValid;
}
/**
* Clone the current object
*/
public LimitsWrapper clone()
{
try
{
return (LimitsWrapper)super.clone();
}
catch ( CloneNotSupportedException e )
{
return null;
}
}
/**
* LimitsWrapper are ordered objects
* @see Object#equals(Object)
*/
public boolean equals( Object that )
{
// Quick test
if ( this == that )
{
return true;
}
if ( that instanceof LimitsWrapper )
{
LimitsWrapper thatInstance = (LimitsWrapper)that;
// Check the prefix first
if ( prefix != thatInstance.prefix )
{
return false;
}
// The selector
if ( selector != thatInstance.selector )
{
return false;
}
// Same selector. Depending on the type, check the two instance
switch ( selector )
{
case DNSPEC :
if ( ( dnSpecStyle != thatInstance.dnSpecStyle ) || ( dnSpecType != thatInstance.dnSpecType ) )
{
return false;
}
// Check the pattern
if ( selectorPattern != thatInstance.selectorPattern )
{
return false;
}
break;
case GROUP :
// If we have an ObjectClass, check it
if ( ( objectClass != null ) && ( !objectClass.equals( thatInstance.objectClass ) ) )
{
return false;
}
else if ( thatInstance.objectClass != null )
{
return false;
}
// If we have an AttributeType, check it
if ( ( attributeType != null ) && ( !attributeType.equals( thatInstance.attributeType ) ) )
{
return false;
}
else if ( thatInstance.attributeType != null )
{
return false;
}
// Check the pattern
if ( selectorPattern != thatInstance.selectorPattern )
{
return false;
}
break;
case ANY :
case ANONYMOUS :
case USERS :
break;
}
// Check the limits now
if ( limits.size() != thatInstance.limits.size() )
{
return false;
}
// Iterate on both limits (they are not ordered... This is a O(n2) loop.
for ( LimitWrapper limit : limits )
{
boolean found = false;
for ( LimitWrapper thatLimit : thatInstance.limits )
{
if ( limit.equals( thatLimit ) )
{
found = true;
break;
}
}
if ( !found )
{
return false;
}
}
return true;
}
else
{
return false;
}
}
/**
* @see Object#hashCode()
*/
public int hashCode()
{
int h = 37;
h += h*17 + selector.hashCode();
// The selector
switch ( selector )
{
case DNSPEC :
if ( dnSpecType != null )
{
h += h*17 + dnSpecType.hashCode();
}
if ( dnSpecStyle != null )
{
h += h*17 + dnSpecStyle.hashCode();
}
break;
case GROUP :
if ( selectorPattern != null )
{
h += h*17 + selectorPattern.hashCode();
}
break;
}
// The limits
for ( LimitWrapper limit : limits )
{
h += h*17 + limit.hashCode();
}
return h;
}
/**
* @see Comparable#compareTo()
*/
public int compareTo( LimitsWrapper that )
{
if ( that == null )
{
return 1;
}
// Check the prefix
if ( prefix < that.prefix )
{
return -1;
}
else if ( prefix > that.prefix )
{
return 1;
}
else
{
return 0;
}
}
/**
* @see Object#toString()
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( '{' ).append( prefix ).append( '}' );
sb.append( selector.getName() );
// The selector
switch ( selector )
{
case ANY :
case ANONYMOUS :
case USERS :
break;
case DNSPEC :
if ( dnSpecType != null )
{
sb.append( '.' ).append( dnSpecType.getName() );
}
if ( dnSpecStyle != null )
{
sb.append( '.' ).append( dnSpecStyle.getName() );
}
sb.append( "=\"" );
sb.append( selectorPattern );
sb.append( '\"' );
break;
case GROUP :
if ( objectClass != null )
{
sb.append( '/' ).append( objectClass );
}
if ( attributeType != null )
{
sb.append( '/' ).append( attributeType );
}
sb.append( "=\"" );
sb.append( selectorPattern );
sb.append( '\"' );
break;
}
// The limits
for ( LimitWrapper limit : limits )
{
sb.append( ' ' );
sb.append( limit );
}
return sb.toString();
}
}