/*
* Copyright (C) 2008 Universidade Federal de Campina Grande
*
* This file is part of OurGrid.
*
* OurGrid is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.ourgrid.peer.business.controller.matcher;
import java.util.Map;
import java.util.StringTokenizer;
import org.ourgrid.common.exception.TokenErrorException;
import org.ourgrid.common.specification.main.JDLTagsPublisher;
import org.ourgrid.common.util.CommonUtils;
import org.ourgrid.common.util.StringUtil;
import condor.classad.ClassAd;
import condor.classad.ClassAdParser;
import condor.classad.Expr;
import condor.classad.RecordExpr;
/**
* The Matcher is responsible for evaluate an expression (i.e, given a set of
* attributes, verify if the expression matches with them)
*/
public class MatcherImpl implements Matcher {
/** String Tokenizer (delimited by spaces) of the expression. */
private StringTokenizer exprTokens;
/** The current token of the expression. */
private String currentExprToken = "";
/** The expression to be evaluated. */
private String expression;
/** The attributes used to make a match with. */
private Map<String,String> attributesMap;
/**
* Sets the currentExprToken to be the next token of the expression
*/
private void getToken() {
if ( exprTokens.hasMoreTokens() ) {
currentExprToken = exprTokens.nextToken();
} else {
currentExprToken = "";
}
}
/**
* Verifies if an expression matches with a set of attributes, in other
* words, evaluate an expression.
*
* @param jobRequirement
* @param machineAtt
* @return <code>true</code> if the expression matches with the attributes
* and <code>false</code> otherwise.
* @throws TokenErrorException If the expression is not valid
*/
public boolean match( String jobRequirement, Map<String,String> machineAtt ) {
if (jobRequirement != null) {
jobRequirement = jobRequirement.toLowerCase();
}
this.expression = StringUtil.insertSpaces( jobRequirement );
Map<String, String> lowerAttributes = CommonUtils.createSerializableMap();
for (Map.Entry<String, String> entry : machineAtt.entrySet()) {
lowerAttributes.put(entry.getKey().toLowerCase(), entry.getValue().toLowerCase());
}
this.attributesMap = lowerAttributes;
if ( this.expression == null ) {
return true;
}
int result = ATT_TRUE;
exprTokens = new StringTokenizer( expression );
try {
if ( exprTokens.countTokens() != 0 ) {
getToken();
result = evalExpr();
if ( !currentExprToken.equals( "" ) ) {
throw new TokenErrorException( expression, currentExprToken );
}
}
} catch ( TokenErrorException e ) {
return false;
}
return (result == ATT_FALSE || result == ATT_UNDEFINED) ? false : true;
}
private int threeValuedOR( int value1, int value2 ) {
// If at least one sentence is TRUE, the result is TRUE
if ( value1 == ATT_TRUE || value2 == ATT_TRUE ) {
return ATT_TRUE;
} else if ( value1 == ATT_FALSE && value2 == ATT_FALSE ) {
// If both sentences are FALSE, the result is FALSE
return ATT_FALSE;
} else {
// otherwise
return ATT_UNDEFINED;
}
}
private int threeValuedAND( int value1, int value2 ) {
// If at least one sentence is FALSE, the result is FALSE
if ( value1 == ATT_FALSE || value2 == ATT_FALSE ) {
return ATT_FALSE;
} else if ( value1 == ATT_TRUE && value2 == ATT_TRUE ) {
// If both sentences are TRUE, the result is true
return ATT_TRUE;
} else {
// otherwise
return ATT_UNDEFINED;
}
}
/**
* Evaluates the expression, making a match between the expression and the
* attributes
*
* @return <code>true</code> if the result of the matche is true
* <code>false</code> otherwise.
* @throws TokenErrorException If the expression is not valid
*/
private int evalExpr() throws TokenErrorException {
int result = evalTerm();
while ( !currentExprToken.equals( "" )
&& (currentExprToken.equals( "||" ) || currentExprToken.equalsIgnoreCase( "OR" )) ) {
getToken();
int result2 = evalTerm();
result = threeValuedOR( result, result2 );
}
return result;
}
/**
* Evaluates a term of the expression. It is used by the evalExpr to make
* the match
*
* @return <code>true</code> if the term evaluate results in true and
* <code>false</code> otherwise
* @throws TokenErrorException if the term is not valid
*/
private int evalTerm() throws TokenErrorException {
int result = evalFactor();
while ( !currentExprToken.equals( "" )
&& (currentExprToken.equals( "&&" ) || currentExprToken.equalsIgnoreCase( "AND" )) ) {
getToken();
int result2 = evalFactor();
result = threeValuedAND( result, result2 );
}
return result;
}
/**
* Evaluates a factor of the expression. It is used by the evalTerm to make
* the match
*
* @return <code>true</code> if the factor evaluate results in true and
* <code>false</code> otherwise
* @throws TokenErrorException if the factor is not valid
*/
private int evalFactor() throws TokenErrorException {
int result;
boolean negate = false;
if ( currentExprToken.equals( "!" ) || currentExprToken.equalsIgnoreCase( "NOT" ) ) {
getToken();
negate = true;
}
if ( currentExprToken.equals( "(" ) ) {
getToken();
result = evalExpr();
if ( !currentExprToken.equals( ")" ) ) {
throw new TokenErrorException( expression, currentExprToken );
}
} else {
result = ATT_UNDEFINED;
if ( !currentExprToken.equals( "" ) ) {
String attName = currentExprToken;
getToken();
String operator = currentExprToken;
getToken();
String attValue = currentExprToken;
String machineAttValue = this.attributesMap.get( attName );
try {
if ( machineAttValue != null )
if ( operator.equals( "==" ) || operator.equals( "=" ) ) {
result = executeEqualsOperation( machineAttValue, attValue );
} else if ( operator.equals( "!=" ) ) {
result = executeNotEqualsOperation( machineAttValue, attValue );
} else if ( operator.equals( ">" ) ) {
result = executeGreaterThenOperation( machineAttValue, attValue );
} else if ( operator.equals( "<" ) ) {
result = executeLessThenOperation( machineAttValue, attValue );
} else if ( operator.equals( ">=" ) ) {
result = executeGreaterOrEqualsThenOperation( machineAttValue, attValue );
} else if ( operator.equals( "<=" ) ) {
result = executeLessOrEqualsThenOperation( machineAttValue, attValue );
} else {
throw new TokenErrorException( expression, "Operator \"" + operator + "\" cannot be used." );
}
} catch (TokenErrorException tee) {
result = ATT_FALSE;
}
}
}
getToken();
if ( result == ATT_UNDEFINED ) {
return ATT_UNDEFINED;
} else if ( result == ATT_TRUE ) {
return (negate ? ATT_FALSE : ATT_TRUE);
} else
// ATT_FALSE
return (negate ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation ">" at two string arguments, but before, it tries
* to convert them to integer values and throws the exception if it could
* not be made.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is greater then the
* second.
* @throws TokenErrorException If the attributes could not be converted to
* integer values.
*/
private int executeGreaterThenOperation( String machineAttValue, String attValue ) throws TokenErrorException {
if ( machineAttValue == null )
return ATT_UNDEFINED;
int attValueInt = testValueIsNumber( attValue, ">" );
int machineAttValueInt = testValueIsNumber( machineAttValue, ">" );
return (machineAttValueInt > attValueInt ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation ">=" at two string arguments, but before, it tries
* to convert them to integer values and throws the exception if it could
* not be made.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is greater or equals
* then the second.
* @throws TokenErrorException If the attributes could not be converted to
* integer values.
*/
private int executeGreaterOrEqualsThenOperation( String machineAttValue, String attValue )
throws TokenErrorException {
if ( machineAttValue == null )
return ATT_UNDEFINED;
int attValueInt = testValueIsNumber( attValue, ">=" );
int machineAttValueInt = testValueIsNumber( machineAttValue, ">=" );
return (machineAttValueInt >= attValueInt ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation "<" at two string arguments, but before, it tries
* to convert them to integer values and throws the exception if it could
* not be made.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is less then the
* second.
* @throws TokenErrorException If the attributes could not be converted to
* integer values.
*/
private int executeLessThenOperation( String machineAttValue, String attValue ) throws TokenErrorException {
if ( machineAttValue == null )
return ATT_UNDEFINED;
int attValueInt = testValueIsNumber( attValue, "<" );
int machineAttValueInt = testValueIsNumber( machineAttValue, "<" );
return (machineAttValueInt < attValueInt ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation "<=" at two string arguments, but before, it
* tries to convert them to integer values and throws the exception if it
* could not be made.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is less or equals then
* the second.
* @throws TokenErrorException If the attributes could not be converted to
* integer values.
*/
private int executeLessOrEqualsThenOperation( String machineAttValue, String attValue ) throws TokenErrorException {
if ( machineAttValue == null )
return ATT_UNDEFINED;
int attValueInt = testValueIsNumber( attValue, "<=" );
int machineAttValueInt = testValueIsNumber( machineAttValue, "<=" );
return (machineAttValueInt <= attValueInt ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation equals at the two string arguments.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is equals to the
* second. <code>false</code> if the the attributes are different
* or if the machineAttValue is null - it means that the attribute
* in question does not exists in machine.
*/
private int executeEqualsOperation( String machineAttValue, String attValue ) {
if ( machineAttValue == null )
return ATT_UNDEFINED;
return (machineAttValue.equalsIgnoreCase( attValue ) ? ATT_TRUE : ATT_FALSE);
}
/**
* Executes the operation not equals at the two string arguments.
*
* @param machineAttValue The value to the attribute to be analyzed at the
* machine.
* @param attValue The value to the attribute to be analyzed at the
* expression.
* @return <code>true</code> if the first attribute is not equals to the
* second or if the machineAttValue is null - it means that the
* attribute in question does not exists in machine.
* <code>false</code> if the attributes are equals.
*/
private int executeNotEqualsOperation( String machineAttValue, String attValue ) {
if ( machineAttValue == null )
return ATT_UNDEFINED;
return (machineAttValue.equalsIgnoreCase( attValue ) ? ATT_FALSE : ATT_TRUE);
}
/**
* Will try to return the integer value for a string attribute value.
*
* @param attValue The string attribute to be converted to integer value.
* @param operator The symbol of the operator that will operate at the
* attribute. It is used only to throws a better exception message if
* it happens.
* @return The integer value for the string attribute.
* @throws TokenErrorException If the string value could not be converted to
* a integer value.
*/
private int testValueIsNumber( String attValue, String operator ) throws TokenErrorException {
int toReturn;
try {
toReturn = Integer.parseInt( attValue );
} catch ( NumberFormatException nfex ) {
throw new TokenErrorException( expression, "Value \"" + attValue
+ "\" is not appliable for the operator \"" + operator + "\"" );
}
return toReturn;
}
/**
* {@inheritDoc}
*/
public int match(String jdlExpression, String machineClassAd) {
assert jdlExpression != null: "JDL Expression must not be null";
assert machineClassAd != null: "Machine ClassAd must not be null";
assert !(jdlExpression.length() == 0): "JDL Expression must not be empty";
assert !(machineClassAd.length() == 0): "Machine ClassAd must not be empty";
Expr jdl = new ClassAdParser(jdlExpression).parse();
RecordExpr classAd = JDLTagsPublisher.buildExprWithTagsToPublish( machineClassAd );
int[] result = ClassAd.match(jdl, classAd);
return result == null? -1 : result[0];
}
}