/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.rdf.internal.constraints;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil;
import com.bigdata.bop.BOp;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IConstant;
import com.bigdata.bop.IValueExpression;
import com.bigdata.bop.NV;
import com.bigdata.rdf.error.SparqlTypeErrorException;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.model.BigdataLiteral;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.sparql.ast.GlobalAnnotations;
/**
* @see http://www.w3.org/2009/sparql/docs/query-1.1/rq25.xml#func-replace
*/
public class ReplaceBOp extends IVValueExpression<IV> implements INeedsMaterialization {
private static final transient Logger log = Logger.getLogger(ReplaceBOp.class);
public interface Annotations extends XSDBooleanIVValueExpression.Annotations {
/**
* The cached regex pattern.
*/
public String PATTERN = ReplaceBOp.class.getName()
+ ".pattern";
}
private static Map<String,Object> anns(
final IValueExpression<? extends IV> pattern,
final IValueExpression<? extends IV> flags,
final GlobalAnnotations globals) {
if (pattern instanceof IConstant &&
(flags == null || flags instanceof IConstant)) {
final IV parg = ((IConstant<IV>) pattern).get();
final IV farg = flags != null ?
((IConstant<IV>) flags).get() : null;
if (parg.hasValue() && (farg == null || farg.hasValue())) {
final Value pargVal = parg.getValue();
final Value fargVal = farg != null ? farg.getValue() : null;
return anns(globals,
new NV(Annotations.PATTERN,
getPattern(pargVal, fargVal)));
}
}
return anns(globals);
}
/**
* Construct a replace bop without flags.
*/
@SuppressWarnings("rawtypes")
public ReplaceBOp(
final IValueExpression<? extends IV> var,
final IValueExpression<? extends IV> pattern,
final IValueExpression<? extends IV> replacement,
final GlobalAnnotations globals) {
this(new BOp[] { var, pattern, replacement }, anns(pattern, null, globals));
}
/**
* Construct a replace bop with flags.
*/
@SuppressWarnings("rawtypes")
public ReplaceBOp(
final IValueExpression<? extends IV> var,
final IValueExpression<? extends IV> pattern,
final IValueExpression<? extends IV> replacement,
final IValueExpression<? extends IV> flags,
final GlobalAnnotations globals) {
this(new BOp[] { var, pattern, replacement, flags }, anns(pattern, flags, globals));
}
/**
* Required shallow copy constructor.
*/
public ReplaceBOp(final BOp[] args, final Map<String, Object> anns) {
super(args, anns);
if (args.length < 2 || args[0] == null || args[1] == null)
throw new IllegalArgumentException();
}
/**
* Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}.
*/
public ReplaceBOp(final ReplaceBOp op) {
super(op);
}
@Override
public Requirement getRequirement() {
return Requirement.SOMETIMES;
}
@Override
@SuppressWarnings("rawtypes")
public IV get(final IBindingSet bs) {
@SuppressWarnings("rawtypes")
final Literal var = getAndCheckLiteralValue(0, bs);
@SuppressWarnings("rawtypes")
final Literal pattern = getAndCheckLiteralValue(1, bs);
@SuppressWarnings("rawtypes")
final Literal replacement = getAndCheckLiteralValue(2, bs);
@SuppressWarnings("rawtypes")
final Literal flags = arity() > 3 ? getAndCheckLiteralValue(3, bs) : null;
if (log.isDebugEnabled()) {
log.debug("var: " + var);
log.debug("pattern: " + pattern);
log.debug("replacement: " + replacement);
log.debug("flags: " + flags);
}
try {
final BigdataLiteral l =
evaluate(getValueFactory(), var, pattern, replacement, flags);
return super.asIV(l, bs);
} catch (ValueExprEvaluationException ex) {
throw new SparqlTypeErrorException();
}
}
/**
* Lifted directly from Sesame's Replace operator.
*
* FIXME The Pattern should be cached if the pattern argument and flags are
* constants.
*
* @see <a href="http://sourceforge.net/apps/trac/bigdata/ticket/516">
* REGEXBOp should cache the Pattern when it is a constant </a>
*/
public BigdataLiteral evaluate(final BigdataValueFactory valueFactory, final Value... args)
throws ValueExprEvaluationException {
if (args.length < 3 || args.length > 4) {
throw new ValueExprEvaluationException(
"Incorrect number of arguments for REPLACE: " + args.length);
}
try {
Literal arg = (Literal) args[0];
Literal pattern = (Literal) args[1];
Literal replacement = (Literal) args[2];
Literal flags = null;
if (args.length == 4) {
flags = (Literal) args[3];
}
if (!QueryEvaluationUtil.isStringLiteral(arg)) {
throw new ValueExprEvaluationException(
"incompatible operand for REPLACE: " + arg);
}
if (!QueryEvaluationUtil.isSimpleLiteral(replacement)) {
throw new ValueExprEvaluationException(
"incompatible operand for REPLACE: " + replacement);
}
String argString = arg.getLabel();
String replacementString = replacement.getLabel();
Pattern p = (Pattern) getProperty(Annotations.PATTERN);
if (p == null) {
p = getPattern(pattern, flags);
}
String result = p.matcher(argString).replaceAll(replacementString);
String lang = arg.getLanguage();
URI dt = arg.getDatatype();
if (lang != null) {
return valueFactory.createLiteral(result, lang);
} else if (dt != null) {
return valueFactory.createLiteral(result, dt);
} else {
return valueFactory.createLiteral(result);
}
} catch (ClassCastException e) {
throw new ValueExprEvaluationException("literal operands expected",
e);
}
}
private static Pattern getPattern(final Value pattern, final Value flags)
throws IllegalArgumentException {
if (!QueryEvaluationUtil.isSimpleLiteral(pattern)) {
throw new IllegalArgumentException(
"incompatible operand for REPLACE: " + pattern);
}
String flagString = null;
if (flags != null) {
if (!QueryEvaluationUtil.isSimpleLiteral(flags)) {
throw new IllegalArgumentException(
"incompatible operand for REPLACE: " + flags);
}
flagString = ((Literal) flags).getLabel();
}
String patternString = ((Literal) pattern).getLabel();
int f = 0;
if (flagString != null) {
for (char c : flagString.toCharArray()) {
switch (c) {
case 's':
f |= Pattern.DOTALL;
break;
case 'm':
f |= Pattern.MULTILINE;
break;
case 'i':
f |= Pattern.CASE_INSENSITIVE;
break;
case 'x':
f |= Pattern.COMMENTS;
break;
case 'd':
f |= Pattern.UNIX_LINES;
break;
case 'u':
f |= Pattern.UNICODE_CASE;
break;
default:
throw new IllegalArgumentException(flagString);
}
}
}
Pattern p = Pattern.compile(patternString, f);
return p;
}
}