View Javadoc

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  public class AntStyleParser
51      implements Parser
52  {
53  
54      /** Grammar constants */
55      static final String VAR_START = "${";
56  
57      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          List<Appender> appenders = new ArrayList<Appender>();
73          int prev = 0;
74          int pos = 0;
75          while ( ( pos = pattern.indexOf( VAR_START, pos ) ) >= 0 )
76          {
77              // Add text between beginning/end of last variable
78              if ( pos > prev )
79              {
80                  appenders.add( new TextAppender( pattern.substring( prev, pos ) ) );
81              }
82  
83              // Move to real variable name beginning
84              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              int endVariable = pattern.indexOf( VAR_CLOSE, pos );
89              if ( endVariable < 0 )
90              {
91                  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              int nextVariable = pattern.indexOf( VAR_START, pos );
97              // Just used to throw exception with more accurate message
98              int lastEndVariable = endVariable;
99              boolean hasNested = false;
100             while ( nextVariable >= 0 && nextVariable < endVariable )
101             {
102                 hasNested = true;
103                 endVariable = pattern.indexOf( VAR_CLOSE, endVariable + VAR_CLOSE_LEN );
104                 // Something is badly closed
105                 if ( endVariable < 0 )
106                 {
107                     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                 nextVariable = pattern.indexOf( VAR_START, nextVariable + VAR_START_LEN );
111                 lastEndVariable = endVariable;
112             }
113             // The chunk to process
114             final String rawKey = pattern.substring( pos - VAR_START_LEN, endVariable + VAR_CLOSE_LEN );
115             // Key without variable start and end symbols
116             final String key = pattern.substring( pos, endVariable );
117 
118             int pipeIndex = key.indexOf( PIPE_SEPARATOR );
119 
120             boolean hasKeyVariables = false;
121             boolean hasDefault = false;
122             boolean hasDefaultVariables = false;
123 
124             // There is a pipe
125             if ( pipeIndex >= 0 )
126             {
127                 // No nested property detected, simple default part
128                 if ( !hasNested )
129                 {
130                     hasDefault = true;
131                     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                     int nextStartKeyVariable = key.indexOf( VAR_START );
139                     hasKeyVariables = pipeIndex > nextStartKeyVariable;
140                     if ( hasKeyVariables )
141                     {
142                         // ff${fdf}|${f}
143                         int nextEndKeyVariable = key.indexOf( VAR_CLOSE, nextStartKeyVariable + VAR_START_LEN );
144                         pipeIndex = key.indexOf( PIPE_SEPARATOR, pipeIndex + PIPE_SEPARATOR_LEN );
145                         while ( pipeIndex >= 0 && pipeIndex > nextStartKeyVariable )
146                         {
147                             pipeIndex = key.indexOf( PIPE_SEPARATOR, nextEndKeyVariable + VAR_CLOSE_LEN );
148                             nextStartKeyVariable = key.indexOf( VAR_START, nextStartKeyVariable + VAR_START_LEN );
149                             // No more nested variable
150                             if ( nextStartKeyVariable < 0 )
151                             {
152                                 break;
153                             }
154                             nextEndKeyVariable = key.indexOf( VAR_CLOSE, nextEndKeyVariable + VAR_CLOSE_LEN );
155                             if ( nextEndKeyVariable < 0 )
156                             {
157                                 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                     if ( pipeIndex >= 0 )
166                     {
167                         hasDefault = true;
168                         hasDefaultVariables = key.indexOf( VAR_START, pipeIndex ) >= 0;
169                     }
170 
171                 }
172             }
173             // No pipe, there is key variables if nested elements have been
174             // detected
175             else
176             {
177                 hasKeyVariables = hasNested;
178             }
179 
180             // Construct variable appenders
181             String keyPart = null;
182             String defaultPart = null;
183             if ( hasDefault )
184             {
185                 keyPart = key.substring( 0, pipeIndex ).trim();
186                 defaultPart = key.substring( pipeIndex + PIPE_SEPARATOR_LEN ).trim();
187             }
188             else
189             {
190                 keyPart = key.trim();
191             }
192             // Choose TextAppender when relevant to avoid unecessary parsing when it's clearly not needed
193             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             prev = endVariable + VAR_CLOSE_LEN;
199             pos = prev;
200         }
201 
202         if ( prev < pattern.length() )
203         {
204             appenders.add( new TextAppender( pattern.substring( prev ) ) );
205         }
206 
207         return appenders.size() == 1 ? appenders.get( 0 ) : new MixinAppender( pattern, appenders );
208     }
209 
210 }