package com.scrivenvar.processors;
import com.scrivenvar.Services;
import com.scrivenvar.preferences.UserPreferences;
import com.scrivenvar.service.Options;
import com.scrivenvar.service.events.Notifier;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.scrivenvar.Constants.STATUS_PARSE_ERROR;
import static com.scrivenvar.Messages.get;
import static com.scrivenvar.decorators.RVariableDecorator.PREFIX;
import static com.scrivenvar.decorators.RVariableDecorator.SUFFIX;
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
import static java.lang.Math.min;
import static java.lang.String.format;
public final class InlineRProcessor extends DefinitionProcessor {
private static final Notifier sNotifier = Services.load( Notifier.class );
private static final Options sOptions = Services.load( Options.class );
private static final int MAX_CACHED_R_STATEMENTS = 512;
private final Map<String, Object> mEvalCache = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(
final Map.Entry<String, Object> eldest ) {
return size() > MAX_CACHED_R_STATEMENTS;
}
};
private static final ScriptEngine ENGINE =
(new ScriptEngineManager()).getEngineByName( "Renjin" );
private static final int PREFIX_LENGTH = PREFIX.length();
private final AtomicBoolean mDirty = new AtomicBoolean( false );
public InlineRProcessor(
final Processor<String> processor,
final Map<String, String> map ) {
super( processor, map );
bootstrapScriptProperty().addListener(
( ob, oldScript, newScript ) -> setDirty( true ) );
workingDirectoryProperty().addListener(
( ob, oldScript, newScript ) -> setDirty( true ) );
getUserPreferences().addSaveEventHandler( ( handler ) -> {
if( isDirty() ) {
init();
setDirty( false );
}
} );
init();
}
private void init() {
getNotifier().clear();
try {
final var bootstrap = getBootstrapScript();
if( !bootstrap.isBlank() ) {
final var wd = getWorkingDirectory();
final var dir = wd.toString().replace( '\\', '/' );
final var map = getDefinitions();
map.put( "$application.r.working.directory$", dir );
eval( replace( bootstrap, map ) );
}
} catch( final Exception ex ) {
getNotifier().notify( ex );
}
}
private void setDirty( final boolean dirty ) {
mDirty.set( dirty );
}
private boolean isDirty() {
return mDirty.get();
}
@Override
public String process( final String text ) {
getNotifier().clear();
final int length = text.length();
final StringBuilder sb = new StringBuilder( length * 2 );
int prevIndex = 0;
int currIndex = text.indexOf( PREFIX );
while( currIndex >= 0 ) {
sb.append( text, prevIndex, currIndex );
prevIndex = currIndex + PREFIX_LENGTH;
currIndex = text.indexOf( SUFFIX, min( currIndex + 1, length ) );
if( currIndex > 1 ) {
final String r = text.substring( prevIndex, currIndex );
try {
final Object result = evalText( r );
sb.append( result );
} catch( final Exception e ) {
sb.append( PREFIX ).append( r ).append( SUFFIX );
getNotifier().notify(
get( STATUS_PARSE_ERROR, e.getMessage(), currIndex )
);
}
prevIndex = currIndex + 1;
}
currIndex = text.indexOf( PREFIX, min( currIndex + 1, length ) );
}
return sb.append( text.substring( min( prevIndex, length ) ) ).toString();
}
private Object evalText( final String r ) {
return mEvalCache.computeIfAbsent( r, v -> eval( r ) );
}
private Object eval( final String r ) {
try {
return getScriptEngine().eval( r );
} catch( final ScriptException ex ) {
final String expr = r.substring( 0, min( r.length(), 30 ) );
final String msg = format(
"Error with [%s...]: %s", expr, ex.getMessage() );
getNotifier().notify( msg );
}
return "";
}
private Path getWorkingDirectory() {
return getUserPreferences().getRDirectory().toPath();
}
private ObjectProperty<File> workingDirectoryProperty() {
return getUserPreferences().rDirectoryProperty();
}
private String getBootstrapScript() {
return getUserPreferences().getRScript();
}
private StringProperty bootstrapScriptProperty() {
return getUserPreferences().rScriptProperty();
}
private UserPreferences getUserPreferences() {
return sOptions.getUserPreferences();
}
private ScriptEngine getScriptEngine() {
return ENGINE;
}
private Notifier getNotifier() {
return sNotifier;
}
}