| /* |
| * 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.felix.webconsole.internal.filter; |
| |
| |
| import java.io.FilterWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| |
| import org.apache.felix.webconsole.DefaultVariableResolver; |
| import org.apache.felix.webconsole.VariableResolver; |
| |
| |
| /** |
| * The <code>ResourceFilteringWriter</code> is a writer, which translates |
| * strings of the form <code>${some key text}</code> to a translation |
| * of the respective <i>some key text</i> or to the <i>some key text</i> |
| * itself if no translation is available from a resource bundle. |
| */ |
| class ResourceFilteringWriter extends FilterWriter |
| { |
| |
| /** |
| * normal processing state, $ signs are recognized here |
| * proceeds to {@link #STATE_DOLLAR} if a $ sign is encountered |
| * proceeds to {@link #STATE_ESCAPE} if a \ sign is encountered |
| * otherwise just writes the character |
| */ |
| private static final int STATE_NULL = 0; |
| |
| /** |
| * State after a $ sign has been recognized |
| * proceeds to {@value #STATE_BUFFERING} if a { sign is encountered |
| * otherwise proceeds to {@link #STATE_NULL} and writes the $ sign and |
| * the current character |
| */ |
| private static final int STATE_DOLLAR = 1; |
| |
| /** |
| * buffers characters until a } is encountered |
| * proceeds to {@link #STATE_NULL} if a } sign is encountered and |
| * translates and writes buffered text before returning |
| * otherwise collects characters to gather the translation key |
| */ |
| private static final int STATE_BUFFERING = 2; |
| |
| /** |
| * escaping the next character, if the character is a $ sign, the |
| * $ sign is writeted. otherwise the \ and the next character is |
| * written |
| * proceeds to {@link #STATE_NULL} |
| */ |
| private static final int STATE_ESCAPE = 3; |
| |
| /** |
| * The ResourceBundle used for translation |
| */ |
| private final ResourceBundle locale; |
| |
| private final VariableResolver variables; |
| |
| /** |
| * The buffer to gather the text to be translated |
| */ |
| private final StringBuffer lineBuffer = new StringBuffer(); |
| |
| /** |
| * The current state, starts with {@link #STATE_NULL} |
| */ |
| private int state = STATE_NULL; |
| |
| |
| ResourceFilteringWriter( final Writer out, final ResourceBundle locale, final VariableResolver variables ) |
| { |
| super( out ); |
| this.locale = locale; |
| this.variables = ( variables != null ) ? variables : new DefaultVariableResolver(); |
| } |
| |
| |
| /** |
| * Write a single character following the state machine: |
| * <table> |
| * <tr><th>State</th><th>Character</th><th>Task</th><th>Next State</th></tr> |
| * <tr><td>NULL</td><td>$</td><td> </td><td>DOLLAR</td></tr> |
| * <tr><td>NULL</td><td>\</td><td> </td><td>ESCAPE</td></tr> |
| * <tr><td>NULL</td><td>any</td><td>write c</td><td>NULL</td></tr> |
| * <tr><td>DOLLAR</td><td>{</td><td> </td><td>BUFFERING</td></tr> |
| * <tr><td>DOLLAR</td><td>any</td><td>write $ and c</td><td>NULL</td></tr> |
| * <tr><td>BUFFERING</td><td>}</td><td>translate and write translation</td><td>NULL</td></tr> |
| * <tr><td>BUFFERING</td><td>any</td><td>buffer c</td><td>BUFFERING</td></tr> |
| * <tr><td>ESACPE</td><td>$</td><td>write $</td><td>NULL</td></tr> |
| * <tr><td>ESCAPE</td><td>any</td><td>write \ and c</td><td>NULL</td></tr> |
| * </table> |
| * |
| * @exception IOException If an I/O error occurs |
| */ |
| public void write( int c ) throws IOException |
| { |
| switch ( state ) |
| { |
| case STATE_NULL: |
| if ( c == '$' ) |
| { |
| state = STATE_DOLLAR; |
| } |
| else if ( c == '\\' ) |
| { |
| state = STATE_ESCAPE; |
| } |
| else |
| { |
| out.write( c ); |
| } |
| break; |
| |
| case STATE_DOLLAR: |
| if ( c == '{' ) |
| { |
| state = STATE_BUFFERING; |
| } |
| else |
| { |
| state = STATE_NULL; |
| out.write( '$' ); |
| out.write( c ); |
| } |
| break; |
| |
| case STATE_BUFFERING: |
| if ( c == '}' ) |
| { |
| state = STATE_NULL; |
| super.write( translate() ); |
| } |
| else |
| { |
| lineBuffer.append( ( char ) c ); |
| } |
| break; |
| |
| case STATE_ESCAPE: |
| state = STATE_NULL; |
| if ( c != '$' ) |
| { |
| out.write( '\\' ); |
| } |
| out.write( c ); |
| break; |
| } |
| } |
| |
| |
| /** |
| * Writes each character calling {@link #write(int)} |
| * |
| * @param cbuf Buffer of characters to be written |
| * @param off Offset from which to start reading characters |
| * @param len Number of characters to be written |
| * @exception IOException If an I/O error occurs |
| */ |
| public void write( char cbuf[], int off, int len ) throws IOException |
| { |
| final int limit = off + len; |
| for ( int i = off; i < limit; i++ ) |
| { |
| write( cbuf[i] ); |
| } |
| } |
| |
| |
| /** |
| * Writes each character calling {@link #write(int)} |
| * |
| * @param str String to be written |
| * @param off Offset from which to start reading characters |
| * @param len Number of characters to be written |
| * @exception IOException If an I/O error occurs |
| */ |
| public void write( String str, int off, int len ) throws IOException |
| { |
| final int limit = off + len; |
| for ( int i = off; i < limit; i++ ) |
| { |
| write( str.charAt( i ) ); |
| } |
| } |
| |
| |
| /** |
| * Translates the current buffer contents and returns the translation. |
| * First the name is looked up in the variables, if not found the |
| * resource bundle is queried. If still not found, the buffer contents |
| * is returned unmodified. |
| */ |
| private String translate() |
| { |
| final String key = lineBuffer.toString(); |
| lineBuffer.delete( 0, lineBuffer.length() ); |
| |
| String value = variables.resolve( key ); |
| if ( value == null ) |
| { |
| try |
| { |
| value = locale.getString( key ); |
| } |
| catch ( MissingResourceException mre ) |
| { |
| // ignore and write the key as the value |
| value = key; |
| } |
| } |
| |
| return value; |
| } |
| } |