/**
* Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed 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 net.roboconf.core.commands;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import net.roboconf.core.ErrorCode;
import net.roboconf.core.model.ParsingError;
import net.roboconf.core.model.beans.AbstractApplication;
import net.roboconf.core.utils.Utils;
/**
* @author Vincent Zurczak - Linagora
*/
public class CommandsParser {
private final Logger logger = Logger.getLogger( getClass().getName());
private final List<ParsingError> parsingErrors = new ArrayList<> ();
private final Context context;
final List<AbstractCommandInstruction> instructions = new ArrayList<> ();
/**
* Constructor.
* @param app an application (not null)
* @param commandsFile a file containing commands (not null)
*/
public CommandsParser( AbstractApplication app, File commandsFile ) {
this.context = new Context( app, commandsFile );
parse();
}
/**
* Constructor.
* @param app an application (not null)
* @param instructionsText instructions text (not null)
*/
public CommandsParser( AbstractApplication app, String instructionsText ) {
this.context = new Context( app, null );
if( instructionsText != null )
parse( instructionsText );
}
/**
* @return a non-null list of errors
*/
public List<ParsingError> getParsingErrors() {
List<ParsingError> result = new ArrayList<>( this.parsingErrors );
if( this.instructions.isEmpty())
result.add( 0, new ParsingError( ErrorCode.CMD_NO_INSTRUCTION, this.context.getCommandFile(), 1 ));
return result;
}
/**
* @return the instructions
*/
public List<AbstractCommandInstruction> getInstructions() {
return this.instructions;
}
/**
* Injects context variables in commands text.
* @param line a non-null line
* @param context a non-null map considered as the context
* @return the line, after it was updated.
* <p>
* All the <code>$(sth)</code> variables will have been replaced by the value
* associated with the <i>sth</i> key in <code>context</code>.
* </p>
*/
public static String injectContextVariables( String line, Map<String,String> context ) {
String result = line;
for( Map.Entry<String,String> entry : context.entrySet())
result = result.replace( "$(" + entry.getKey() + ")", entry.getValue());
return result;
}
/**
* Parses the whole file and extracts instructions.
*/
private void parse() {
try {
// We assume these files are not that big.
String fileContent = Utils.readFileContent( this.context.getCommandFile());
parse( fileContent );
} catch( IOException e ) {
this.logger.severe( "A commands file could not be read. File path: " + this.context.getName());
}
}
/**
* Parses the whole file and extracts instructions.
* @param instructionsText a non-null string to parse
*/
private void parse( String instructionsText ) {
// Allow line breaks in commands. But we must keep the lines count.
// So, we replace escaped line breaks by a particular separator.
// They will be used to count lines.
final String sep = "!@!";
instructionsText = instructionsText.replaceAll( "\\\\\n\\s*", sep );
// Parse line by line.
int lineNumber = 0;
for( String string : Utils.splitNicely( instructionsText, "\n" ) ) {
String line = string.trim();
lineNumber ++;
// Remove comments
line = line.replaceFirst( "#.*", "" );
// Skip empty lines
if( line.isEmpty())
continue;
// Update lines count
int lineLength = line.length();
line = line.replace( sep, "" );
int lineCountOffset = (lineLength - line.length()) / sep.length();
// Update the line with variables
line = injectContextVariables( line, this.context.variables );
// Find the instruction
AbstractCommandInstruction instr = parse( line, lineNumber );
if( instr != null ) {
List<ParsingError> errors = instr.validate();
if( errors.isEmpty())
instr.updateContext();
else
this.parsingErrors.addAll( errors );
this.instructions.add( instr );
} else {
this.logger.severe( "An invalid instruction was found in " + this.context.getName() + ": " + line );
this.parsingErrors.add( new ParsingError(
ErrorCode.CMD_UNRECOGNIZED_INSTRUCTION,
this.context.getCommandFile(),
lineNumber,
"Instruction: " + line ));
}
// Update the line number
lineNumber += lineCountOffset;
}
}
/**
* Parses a single line and extracts an instructions when possible.
* @param line a text line
* @param lineNumber the line number
* @return an instruction, or null if none could be recognized
*/
private AbstractCommandInstruction parse( String line, int lineNumber ) {
AbstractCommandInstruction result = null;
String toLowerCase = line.toLowerCase();
if( toLowerCase.startsWith( AssociateTargetCommandInstruction.PREFIX ))
result = new AssociateTargetCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( ChangeStateCommandInstruction.PREFIX ))
result = new ChangeStateCommandInstruction( this.context, line, lineNumber );
else if( BulkCommandInstructions.isBulkInstruction( toLowerCase ))
result = new BulkCommandInstructions( this.context, line, lineNumber );
else if( toLowerCase.startsWith( EmailCommandInstruction.PREFIX ))
result = new EmailCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( DefineVariableCommandInstruction.PREFIX ))
result = new DefineVariableCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( CreateInstanceCommandInstruction.PREFIX ))
result = new CreateInstanceCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( ReplicateCommandInstruction.PREFIX ))
result = new ReplicateCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( RenameCommandInstruction.PREFIX ))
result = new RenameCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( WriteCommandInstruction.WRITE_PREFIX ))
result = new WriteCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( AppendCommandInstruction.APPEND_PREFIX ))
result = new AppendCommandInstruction( this.context, line, lineNumber );
else if( toLowerCase.startsWith( ExecuteCommandInstruction.PREFIX ))
result = new ExecuteCommandInstruction( this.context, line, lineNumber );
return result;
}
}