/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.deltaspike.jsf.util;
import javax.faces.context.FacesContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A filtered stream that evaluates value expressions in the original stream while reading from it.
*/
public class ValueExpressionEvaluationInputStream extends InputStream
{
/**
* Logger for this class.
*/
private static final Logger log = Logger.getLogger(ValueExpressionEvaluationInputStream.class.getName());
private FacesContext facesContext;
private PushbackInputStream wrapped;
private String currentValue;
private int currentValueIndex = -1;
public ValueExpressionEvaluationInputStream(FacesContext facesContext, InputStream inputStream)
{
this.facesContext = facesContext;
this.wrapped = new PushbackInputStream(inputStream, 512);
}
/**
* Reads a byte from the original stream and checks for value expression occurrences.
* A value expression has the following format: #{xxx}
* If a value expression is found, its occurrence in the stream will replaced with
* the evaluated value of the expression.
*
* @return
* @throws java.io.IOException
*/
@Override
public int read() throws IOException
{
// check for a current value
if (currentValueIndex != -1)
{
if (currentValueIndex < currentValue.length())
{
return currentValue.charAt(currentValueIndex++);
}
else
{
// current value exhausted, reset index
currentValueIndex = -1;
}
}
// read byte and check for value expression begin
int c1 = wrapped.read();
if (c1 != '#')
{
return c1; // can't be a value expression, just return the character
}
else
{
// could be a value expression, next character must be '{'
int c2 = wrapped.read();
if (c2 != '{')
{
wrapped.unread(c2); // we did not find a value expression, unread byte that we read too much
return c1; // return original character
}
else
{
// read until '}', '\n' or eof occurs (end of value expression or data)
List<Integer> possibleValueExpression = new LinkedList<Integer>();
int c = wrapped.read();
boolean insideString = (c == '\''); // a '}' inside a string must not terminate the expression string
while (c != -1 && c != '\n' && (insideString || c != '}'))
{
possibleValueExpression.add(c);
c = wrapped.read();
if (c == '\'')
{
insideString = !insideString;
}
}
if (c != '}')
{
// we did not find a value expression, unread bytes that we read too much (in reverse order)
if (c != -1) // we can't unread eof
{
wrapped.unread(c);
}
ListIterator<Integer> it = possibleValueExpression.listIterator(possibleValueExpression.size());
while (it.hasPrevious())
{
wrapped.unread(it.previous());
}
wrapped.unread(c2);
return c1; // return original character
}
else
{
// we found a value expression #{xxx} (xxx is stored in possibleValueExpression)
// create the expression string
String expressionString = createExpressionString(possibleValueExpression);
// evaluate it
String expressionValue = facesContext.getApplication()
.evaluateExpressionGet(facesContext, expressionString, String.class);
if (expressionValue == null)
{
if (log.isLoggable(Level.WARNING))
{
log.warning("ValueExpression " + expressionString + " evaluated to null.");
}
expressionValue = "null"; // fallback value for null
}
// do NOT unread the evaluated value, but rather store it in an internal buffer,
// because otherwise we could recursively evaluate value expressions (a value expression
// that resolves to a string containing "#{...}" would be re-evaluated).
this.currentValue = expressionValue;
// return first character of currentValue, if exists (not an empty string)
if (currentValue.length() != 0)
{
this.currentValueIndex = 0;
return currentValue.charAt(currentValueIndex++);
}
else // currentValue is an empty string
{
// in this case we must recursively start a new read (incl. checks for a new value expression)
this.currentValueIndex = -1;
return read();
}
}
}
}
}
private String createExpressionString(List<Integer> expressionList)
{
char[] expressionChars = new char[expressionList.size() + 3]; // #{expressionList}
int i = 0;
expressionChars[i++] = '#';
expressionChars[i++] = '{';
for (Integer c : expressionList)
{
expressionChars[i++] = (char) c.intValue();
}
expressionChars[i] = '}';
return String.valueOf(expressionChars);
}
}