/******************************************************************************
* Copyright (c) 2016 Oracle
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.sapphire;
import java.util.List;
import org.eclipse.sapphire.util.EqualsFactory;
import org.eclipse.sapphire.util.HashCodeFactory;
import org.eclipse.sapphire.util.ListFactory;
/**
* A version constraint is a boolean expression that can check versions for applicability. In string
* format, it is represented as a comma-separated list of specific versions, closed
* ranges (expressed using "[1.2.3-4.5)" syntax and open ranges (expressed using "[1.2.3" or "4.5)"
* syntax). The square brackets indicate that the range includes the specified version. The parenthesis
* indicate that the range goes up to, but does not actually include the specified version.
*
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
public final class VersionConstraint
{
private static final int SM_RANGE_STARTING = 0;
private static final int SM_RANGE_ENDED = 1;
private static final int SM_VERSION_STARTING = 2;
private static final int SM_VERSION_SEGMENT_STARTING = 3;
private static final int SM_VERSION_SEGMENT_CONTINUING = 4;
private static final int SM_VERSION_ENDED = 5;
private final List<Range> ranges;
public VersionConstraint( final String expr )
{
if( expr == null )
{
throw new IllegalArgumentException();
}
final ListFactory<Range> rangesListFactory = ListFactory.start();
int state = SM_RANGE_STARTING;
Range.Factory range = null;
StringBuilder buf = null;
for( int position = 0, n = expr.length(); position < n; position++ )
{
final char ch = expr.charAt( position );
switch( state )
{
case SM_RANGE_STARTING:
{
if( ch == ' ' )
{
// ignore
}
else if( ch == '[' )
{
range = new Range.Factory();
range.minVersionInclusive = true;
buf = new StringBuilder();
state = SM_VERSION_STARTING;
}
else if( ch == '(' )
{
range = new Range.Factory();
range.minVersionInclusive = false;
buf = new StringBuilder();
state = SM_VERSION_STARTING;
}
else if( ch >= '0' && ch <= '9' )
{
buf = new StringBuilder();
buf.append( ch );
state = SM_VERSION_SEGMENT_CONTINUING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
case SM_RANGE_ENDED:
{
if( ch == ' ' )
{
// ignore
}
else if( ch == ',' )
{
state = SM_RANGE_STARTING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
case SM_VERSION_STARTING:
{
if( ch == ' ' )
{
// ignore
}
else if( ch >= '0' && ch <= '9' )
{
buf.append( ch );
state = SM_VERSION_SEGMENT_CONTINUING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
case SM_VERSION_SEGMENT_STARTING:
{
if( ch >= '0' && ch <= '9' )
{
buf.append( ch );
state = SM_VERSION_SEGMENT_CONTINUING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
case SM_VERSION_SEGMENT_CONTINUING:
{
if( ch >= '0' && ch <= '9' )
{
buf.append( ch );
}
else if( ch == '.' )
{
buf.append( ch );
state = SM_VERSION_SEGMENT_STARTING;
}
else if( ch == ' ' )
{
state = SM_VERSION_ENDED;
}
else if( ch == ']' )
{
if( range == null )
{
range = new Range.Factory();
}
range.maxVersion = new Version( buf.toString() );
range.maxVersionInclusive = true;
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_ENDED;
}
else if( ch == ')' )
{
if( range == null )
{
range = new Range.Factory();
}
range.maxVersion = new Version( buf.toString() );
range.maxVersionInclusive = false;
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_ENDED;
}
else if( ch == '-' )
{
if( range == null )
{
throw new IllegalArgumentException();
}
range.minVersion = new Version( buf.toString() );
buf = new StringBuilder();
state = SM_VERSION_STARTING;
}
else if( ch == ',' )
{
if( range == null )
{
range = new Range.Factory();
range.minVersion = new Version( buf.toString() );
range.maxVersion = range.minVersion;
range.minVersionInclusive = true;
range.maxVersionInclusive = true;
}
else
{
range.minVersion = new Version( buf.toString() );
}
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_STARTING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
case SM_VERSION_ENDED:
{
if( ch == ' ' )
{
// ignore
}
else if( ch == ']' )
{
if( range == null )
{
range = new Range.Factory();
}
range.maxVersion = new Version( buf.toString() );
range.maxVersionInclusive = true;
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_ENDED;
}
else if( ch == ')' )
{
if( range == null )
{
range = new Range.Factory();
}
range.maxVersion = new Version( buf.toString() );
range.maxVersionInclusive = false;
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_ENDED;
}
else if( ch == '-' )
{
if( range == null )
{
throw new IllegalArgumentException();
}
range.minVersion = new Version( buf.toString() );
buf = new StringBuilder();
state = SM_VERSION_STARTING;
}
else if( ch == ',' )
{
if( range == null )
{
range = new Range.Factory();
range.minVersion = new Version( buf.toString() );
range.maxVersion = range.minVersion;
range.minVersionInclusive = true;
range.maxVersionInclusive = true;
}
else
{
range.minVersion = new Version( buf.toString() );
}
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_STARTING;
}
else
{
throw new IllegalArgumentException();
}
break;
}
default:
{
throw new IllegalStateException();
}
}
}
if( state == SM_VERSION_SEGMENT_CONTINUING || state == SM_VERSION_ENDED )
{
if( range == null )
{
range = new Range.Factory();
range.minVersion = new Version( buf.toString() );
range.maxVersion = range.minVersion;
range.minVersionInclusive = true;
range.maxVersionInclusive = true;
}
else
{
range.minVersion = new Version( buf.toString() );
}
rangesListFactory.add( range.create() );
range = null;
buf = null;
state = SM_RANGE_ENDED;
}
if( state != SM_RANGE_ENDED )
{
throw new IllegalArgumentException();
}
this.ranges = rangesListFactory.result();
}
public List<Range> ranges()
{
return this.ranges;
}
public boolean check( final Version version )
{
for( Range subexpr : this.ranges )
{
if( subexpr.check( version ) )
{
return true;
}
}
return false;
}
public boolean check( final String version )
{
return check( new Version( version ) );
}
@Override
public boolean equals( final Object obj )
{
if( obj instanceof VersionConstraint )
{
final VersionConstraint constraint = (VersionConstraint) obj;
return this.ranges.equals( constraint.ranges );
}
return false;
}
@Override
public int hashCode()
{
return this.ranges.hashCode();
}
@Override
public String toString()
{
final StringBuffer buf = new StringBuffer();
for( Range subexpr : this.ranges )
{
if( buf.length() > 0 ) buf.append( ',' );
buf.append( subexpr.toString() );
}
return buf.toString();
}
public static final class Range
{
private final Limit min;
private final Limit max;
private Range( final Limit min,
final Limit max )
{
if( min == null && max == null )
{
throw new IllegalArgumentException();
}
this.min = min;
this.max = max;
}
public Limit min()
{
return this.min;
}
public Limit max()
{
return this.max;
}
public boolean check( final Version version )
{
if( this.min != null )
{
final int res = version.compareTo( this.min.version() );
if( ! ( res > 0 || ( res == 0 && this.min.inclusive() ) ) )
{
return false;
}
}
if( this.max != null )
{
final int res = version.compareTo( this.max.version() );
if( ! ( res < 0 || ( res == 0 && this.max.inclusive() ) ) )
{
return false;
}
}
return true;
}
@Override
public boolean equals( final Object obj )
{
if( obj instanceof Range )
{
final Range range = (Range) obj;
return EqualsFactory.start().add( this.min, range.min ).add( this.max, range.max ).result();
}
return false;
}
@Override
public int hashCode()
{
return HashCodeFactory.start().add( this.min ).add( this.max ).result();
}
@Override
public String toString()
{
if( this.min != null && this.max != null &&
this.min.version().equals( this.max.version() ) &&
this.min.inclusive() == this.max.inclusive() == true )
{
return this.min.version().toString();
}
else
{
final StringBuffer buf = new StringBuffer();
if( this.min != null )
{
buf.append( this.min.inclusive() ? '[' : '(' );
buf.append( this.min.version().toString() );
}
if( this.max != null )
{
if( buf.length() != 0 )
{
buf.append( '-' );
}
buf.append( this.max.version().toString() );
buf.append( this.max.inclusive() ? ']' : ')' );
}
return buf.toString();
}
}
public static final class Limit
{
private final Version version;
private final boolean inclusive;
private Limit( final Version version,
final boolean inclusive )
{
if( version == null )
{
throw new IllegalArgumentException();
}
this.version = version;
this.inclusive = inclusive;
}
public Version version()
{
return this.version;
}
public boolean inclusive()
{
return this.inclusive;
}
@Override
public boolean equals( final Object obj )
{
if( obj instanceof Limit )
{
final Limit limit = (Limit) obj;
return this.version.equals( limit.version ) && this.inclusive == limit.inclusive;
}
return false;
}
@Override
public int hashCode()
{
return this.version.hashCode() ^ Boolean.valueOf( this.inclusive ).hashCode();
}
}
private static final class Factory
{
public Version minVersion;
public boolean minVersionInclusive;
public Version maxVersion;
public boolean maxVersionInclusive;
public Range create()
{
final Limit min = ( this.minVersion == null ? null : new Limit( this.minVersion, this.minVersionInclusive ) );
final Limit max = ( this.maxVersion == null ? null : new Limit( this.maxVersion, this.maxVersionInclusive ) );
return new Range( min, max );
}
}
}
}