Coverage Report - org.apache.onami.configuration.variables.AntStyleParser
 
Classes in this File Line Coverage Branch Coverage Complexity
AntStyleParser
98%
62/63
97%
43/44
24
 
 1  
 package org.apache.onami.configuration.variables;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import static java.lang.String.format;
 23  
 
 24  
 import java.util.ArrayList;
 25  
 import java.util.List;
 26  
 
 27  
 /**
 28  
  * Parser implementation to resolve ant-style variables.
 29  
  *
 30  
  * <h2>Grammar</h2>
 31  
  *
 32  
  * <pre>
 33  
  * expression := (variable|text)*
 34  
  * variable := '${' expression '|' expression '}'
 35  
  * text := // any characters except '${'
 36  
  * </pre>
 37  
  *
 38  
  * <h2>Examples</h2>
 39  
  * <ul>
 40  
  * <li>Mixed expression: <code>${foo} and ${bar}</code></li>
 41  
  * <li>Variable with default value: <code>${foo|bar}</code>, <code>${foo|default value is ${bar}}</code>
 42  
  * <li>Dynamic variable: <code>${${foo.name}}</code></li>
 43  
  * <li>Etc. <code>${foo${bar|}|${other|${${fallback.name}}!}}</code></li>
 44  
  * </ul>
 45  
  *
 46  
  * <h3>Note</h3> The parser trim variable key and default value thus <tt>${ foo  | default     }</tt> is equals to <tt>${foo|default}</tt>.
 47  
  *
 48  
  * @since 6.2
 49  
  */
 50  21638
 public class AntStyleParser
 51  
     implements Parser
 52  
 {
 53  
 
 54  
     /** Grammar constants */
 55  
     static final String VAR_START = "${";
 56  
 
 57  2
     static final int VAR_START_LEN = VAR_START.length();
 58  
 
 59  
     static final char VAR_CLOSE = '}';
 60  
 
 61  
     static final int VAR_CLOSE_LEN = 1;
 62  
 
 63  
     static final char PIPE_SEPARATOR = '|';
 64  
 
 65  
     static final int PIPE_SEPARATOR_LEN = 1;
 66  
 
 67  
     /**
 68  
      * FIXME: Refactor!
 69  
      */
 70  
     public Appender parse( String pattern )
 71  
     {
 72  22596
         List<Appender> appenders = new ArrayList<Appender>();
 73  22596
         int prev = 0;
 74  22596
         int pos = 0;
 75  25078
         while ( ( pos = pattern.indexOf( VAR_START, pos ) ) >= 0 )
 76  
         {
 77  
             // Add text between beginning/end of last variable
 78  2494
             if ( pos > prev )
 79  
             {
 80  434
                 appenders.add( new TextAppender( pattern.substring( prev, pos ) ) );
 81  
             }
 82  
 
 83  
             // Move to real variable name beginning
 84  2494
             pos += VAR_START_LEN;
 85  
 
 86  
             // Next close bracket (not necessarily the variable end bracket if
 87  
             // there is a default value with nested variables
 88  2494
             int endVariable = pattern.indexOf( VAR_CLOSE, pos );
 89  2494
             if ( endVariable < 0 )
 90  
             {
 91  6
                 throw new IllegalArgumentException( format( "Syntax error in property value '%s', missing close bracket '%s' for variable beginning at col %s: '%s'",
 92  
                                                             pattern, VAR_CLOSE, pos - VAR_START_LEN, pattern.substring( pos - VAR_START_LEN ) ) );
 93  
             }
 94  
 
 95  
             // Try to skip eventual internal variable here
 96  2488
             int nextVariable = pattern.indexOf( VAR_START, pos );
 97  
             // Just used to throw exception with more accurate message
 98  2488
             int lastEndVariable = endVariable;
 99  2488
             boolean hasNested = false;
 100  4048
             while ( nextVariable >= 0 && nextVariable < endVariable )
 101  
             {
 102  1566
                 hasNested = true;
 103  1566
                 endVariable = pattern.indexOf( VAR_CLOSE, endVariable + VAR_CLOSE_LEN );
 104  
                 // Something is badly closed
 105  1566
                 if ( endVariable < 0 )
 106  
                 {
 107  6
                     throw new IllegalArgumentException( format( "Syntax error in property value '%s', missing close bracket '%s' for variable beginning at col %s: '%s'",
 108  
                                                                 pattern, VAR_CLOSE, nextVariable, pattern.substring( nextVariable, lastEndVariable ) ) );
 109  
                 }
 110  1560
                 nextVariable = pattern.indexOf( VAR_START, nextVariable + VAR_START_LEN );
 111  1560
                 lastEndVariable = endVariable;
 112  
             }
 113  
             // The chunk to process
 114  2482
             final String rawKey = pattern.substring( pos - VAR_START_LEN, endVariable + VAR_CLOSE_LEN );
 115  
             // Key without variable start and end symbols
 116  2482
             final String key = pattern.substring( pos, endVariable );
 117  
 
 118  2482
             int pipeIndex = key.indexOf( PIPE_SEPARATOR );
 119  
 
 120  2482
             boolean hasKeyVariables = false;
 121  2482
             boolean hasDefault = false;
 122  2482
             boolean hasDefaultVariables = false;
 123  
 
 124  
             // There is a pipe
 125  2482
             if ( pipeIndex >= 0 )
 126  
             {
 127  
                 // No nested property detected, simple default part
 128  1260
                 if ( !hasNested )
 129  
                 {
 130  460
                     hasDefault = true;
 131  460
                     hasDefaultVariables = false;
 132  
                 }
 133  
                 // There is a pipe and nested variable,
 134  
                 // determine if pipe is for the current variable or a nested key
 135  
                 // variable
 136  
                 else
 137  
                 {
 138  800
                     int nextStartKeyVariable = key.indexOf( VAR_START );
 139  800
                     hasKeyVariables = pipeIndex > nextStartKeyVariable;
 140  800
                     if ( hasKeyVariables )
 141  
                     {
 142  
                         // ff${fdf}|${f}
 143  250
                         int nextEndKeyVariable = key.indexOf( VAR_CLOSE, nextStartKeyVariable + VAR_START_LEN );
 144  250
                         pipeIndex = key.indexOf( PIPE_SEPARATOR, pipeIndex + PIPE_SEPARATOR_LEN );
 145  400
                         while ( pipeIndex >= 0 && pipeIndex > nextStartKeyVariable )
 146  
                         {
 147  200
                             pipeIndex = key.indexOf( PIPE_SEPARATOR, nextEndKeyVariable + VAR_CLOSE_LEN );
 148  200
                             nextStartKeyVariable = key.indexOf( VAR_START, nextStartKeyVariable + VAR_START_LEN );
 149  
                             // No more nested variable
 150  200
                             if ( nextStartKeyVariable < 0 )
 151  
                             {
 152  50
                                 break;
 153  
                             }
 154  150
                             nextEndKeyVariable = key.indexOf( VAR_CLOSE, nextEndKeyVariable + VAR_CLOSE_LEN );
 155  150
                             if ( nextEndKeyVariable < 0 )
 156  
                             {
 157  0
                                 throw new IllegalArgumentException( format( "Syntax error in property value '%s', missing close bracket '%s' for variable beginning at col %s: '%s'",
 158  
                                                                             pattern, VAR_CLOSE, nextStartKeyVariable, key.substring( nextStartKeyVariable ) ) );
 159  
                             }
 160  
                         }
 161  
                     }
 162  
 
 163  
                     // nested variables are only for key, current variable does
 164  
                     // not have a default value
 165  800
                     if ( pipeIndex >= 0 )
 166  
                     {
 167  650
                         hasDefault = true;
 168  650
                         hasDefaultVariables = key.indexOf( VAR_START, pipeIndex ) >= 0;
 169  
                     }
 170  
 
 171  800
                 }
 172  
             }
 173  
             // No pipe, there is key variables if nested elements have been
 174  
             // detected
 175  
             else
 176  
             {
 177  1222
                 hasKeyVariables = hasNested;
 178  
             }
 179  
 
 180  
             // Construct variable appenders
 181  2482
             String keyPart = null;
 182  2482
             String defaultPart = null;
 183  2482
             if ( hasDefault )
 184  
             {
 185  1110
                 keyPart = key.substring( 0, pipeIndex ).trim();
 186  1110
                 defaultPart = key.substring( pipeIndex + PIPE_SEPARATOR_LEN ).trim();
 187  
             }
 188  
             else
 189  
             {
 190  1372
                 keyPart = key.trim();
 191  
             }
 192  
             // Choose TextAppender when relevant to avoid unecessary parsing when it's clearly not needed
 193  2482
             appenders.add( new KeyAppender( this,
 194  
                                             rawKey,
 195  
                                             hasKeyVariables ? parse( keyPart ) : new TextAppender( keyPart ),
 196  
                                             !hasDefault ? null : ( hasDefaultVariables ? parse( defaultPart ) : new TextAppender( defaultPart ) ) ) );
 197  
 
 198  2482
             prev = endVariable + VAR_CLOSE_LEN;
 199  2482
             pos = prev;
 200  2482
         }
 201  
 
 202  22584
         if ( prev < pattern.length() )
 203  
         {
 204  20586
             appenders.add( new TextAppender( pattern.substring( prev ) ) );
 205  
         }
 206  
 
 207  22584
         return appenders.size() == 1 ? appenders.get( 0 ) : new MixinAppender( pattern, appenders );
 208  
     }
 209  
 
 210  
 }