/*
* Copyright 2009-2012 The 99 Software Foundation
*
* 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 org.nnsoft.guice.rocoto.variables;
import static java.text.MessageFormat.format;
import java.util.ArrayList;
import java.util.List;
/**
* Parser implementation to resolve ant-style variables.
*
* <h2>Grammar</h2>
*
* <pre>
* expression := (variable|text)*
* variable := '${' expression '|' expression '}'
* text := // any characters except '${'
* </pre>
*
* <h2>Examples</h2>
* <ul>
* <li>Mixed expression: <code>${foo} and ${bar}</code></li>
* <li>Variable with default value: <code>${foo|bar}</code>, <code>${foo|default value is ${bar}}</code>
* <li>Dynamic variable: <code>${${foo.name}}</code></li>
* <li>Etc. <code>${foo${bar|}|${other|${${fallback.name}}!}}</code></li>
* </ul>
*
* <h3>Note</h3> The parser trim variable key and default value thus <tt>${ foo | default }</tt> is equals to <tt>${foo|default}</tt>.
*
*/
public class AntStyleParser implements Parser
{
/** Grammar constants */
static final String VAR_START = "${";
static final int VAR_START_LEN = VAR_START.length();
static final char VAR_CLOSE = '}';
static final int VAR_CLOSE_LEN = 1;
static final char PIPE_SEPARATOR = '|';
static final int PIPE_SEPARATOR_LEN = 1;
/**
* FIXME: Refactor!
*/
public Appender parse( String pattern ) throws IllegalArgumentException
{
List<Appender> appenders = new ArrayList<Appender>();
int prev = 0;
int pos = 0;
while ((pos = pattern.indexOf(VAR_START, pos)) >= 0)
{
// Add text between beginning/end of last variable
if ( pos > prev )
{
appenders.add(new TextAppender(pattern.substring(prev, pos)));
}
// Move to real variable name beginning
pos += VAR_START_LEN;
// Next close bracket (not necessarily the variable end bracket if
// there is a default value with nested variables
int endVariable = pattern.indexOf(VAR_CLOSE, pos);
if ( endVariable < 0 )
{
throw new IllegalArgumentException(format(
"Syntax error in property value ''{0}'', missing close bracket ''{1}'' for variable beginning at col {2}: ''{3}''", pattern,
VAR_CLOSE, pos - VAR_START_LEN, pattern.substring(pos - VAR_START_LEN)));
}
// Try to skip eventual internal variable here
int nextVariable = pattern.indexOf(VAR_START, pos);
// Just used to throw exception with more accurate message
int lastEndVariable = endVariable;
boolean hasNested = false;
while (nextVariable >= 0 && nextVariable < endVariable)
{
hasNested = true;
endVariable = pattern.indexOf(VAR_CLOSE, endVariable + VAR_CLOSE_LEN);
// Something is badly closed
if ( endVariable < 0 )
{
throw new IllegalArgumentException(format(
"Syntax error in property value ''{0}'', missing close bracket ''{1}'' for variable beginning at col {2}: ''{3}''",
pattern, VAR_CLOSE, nextVariable, pattern.substring(nextVariable, lastEndVariable)));
}
nextVariable = pattern.indexOf(VAR_START, nextVariable + VAR_START_LEN);
lastEndVariable = endVariable;
}
// The chunk to process
final String rawKey = pattern.substring(pos - VAR_START_LEN, endVariable + VAR_CLOSE_LEN);
// Key without variable start and end symbols
final String key = pattern.substring(pos, endVariable);
int pipeIndex = key.indexOf(PIPE_SEPARATOR);
boolean hasKeyVariables = false;
boolean hasDefault = false;
boolean hasDefaultVariables = false;
// There is a pipe
if ( pipeIndex >= 0 )
{
// No nested property detected, simple default part
if ( !hasNested )
{
hasDefault = true;
hasDefaultVariables = false;
}
// There is a pipe and nested variable,
// determine if pipe is for the current variable or a nested key
// variable
else
{
int nextStartKeyVariable = key.indexOf(VAR_START);
hasKeyVariables = pipeIndex > nextStartKeyVariable;
if ( hasKeyVariables )
{
// ff${fdf}|${f}
int nextEndKeyVariable = key.indexOf(VAR_CLOSE, nextStartKeyVariable + VAR_START_LEN);
pipeIndex = key.indexOf(PIPE_SEPARATOR, nextEndKeyVariable + VAR_CLOSE_LEN);
while (pipeIndex >= 0 && pipeIndex > nextStartKeyVariable)
{
pipeIndex = key.indexOf(PIPE_SEPARATOR, nextEndKeyVariable + VAR_CLOSE_LEN);
nextStartKeyVariable = key.indexOf(VAR_START, nextStartKeyVariable + VAR_START_LEN);
// No more nested variable
if ( nextStartKeyVariable < 0 )
{
break;
}
nextEndKeyVariable = key.indexOf(VAR_CLOSE, nextEndKeyVariable + VAR_CLOSE_LEN);
if ( nextEndKeyVariable < 0 )
{
throw new IllegalArgumentException(
format("Syntax error in property value ''{0}'', missing close bracket ''{1}'' for variable beginning at col {2}: ''{3}''",
pattern, VAR_CLOSE, nextStartKeyVariable, key.substring(nextStartKeyVariable)));
}
}
}
// nested variables are only for key, current variable does
// not have a default value
if ( pipeIndex >= 0 )
{
hasDefault = true;
hasDefaultVariables = key.indexOf(VAR_START, pipeIndex) >= 0;
}
}
}
// No pipe, there is key variables if nested elements have been
// detected
else
{
hasKeyVariables = hasNested;
}
// Construct variable appenders
String keyPart = null;
String defaultPart = null;
if ( hasDefault )
{
keyPart = key.substring(0, pipeIndex).trim();
defaultPart = key.substring(pipeIndex + PIPE_SEPARATOR_LEN).trim();
} else
{
keyPart = key.trim();
}
// Choose TextAppender when relevant to avoid unecessary parsing when it's clearly not needed
appenders.add(new KeyAppender(this, rawKey, hasKeyVariables ? parse(keyPart) : new TextAppender(keyPart), !hasDefault ? null
: (hasDefaultVariables ? parse(defaultPart) : new TextAppender(defaultPart))));
prev = endVariable + VAR_CLOSE_LEN;
pos = prev;
}
if ( prev < pattern.length() )
{
appenders.add(new TextAppender(pattern.substring(prev)));
}
return appenders.size() == 1 ? appenders.get(0) : new MixinAppender(pattern, appenders);
}
}