View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.properties.spi.config.resolve;
17  
18  import java.util.ArrayList;
19  import java.util.LinkedHashSet;
20  import java.util.Set;
21  
22  import org.apache.commons.lang.StringUtils;
23  
24  import de.smartics.properties.api.config.domain.UnknownPropertyException;
25  import de.smartics.properties.api.core.domain.PropertyValidationException;
26  
27  /**
28   * Replaces place holders by their values.
29   */
30  class Resolver
31  {
32  
33    // ********************************* Fields *********************************
34  
35    // --- constants ------------------------------------------------------------
36  
37    // --- members --------------------------------------------------------------
38  
39    /**
40     * The context that provides access to properties.
41     */
42    private final ResolveContext context;
43  
44    // ****************************** Initializer *******************************
45  
46    // ****************************** Constructors ******************************
47  
48    Resolver(final ResolveContext context)
49    {
50      this.context = context;
51    }
52  
53    // ****************************** Inner Classes *****************************
54  
55    /**
56     * Helper to detect recursion.
57     */
58    private static final class RecursionDetection
59    {
60      // ******************************** Fields ********************************
61  
62      // --- constants ----------------------------------------------------------
63  
64      // --- members ------------------------------------------------------------
65  
66      /**
67       * The already encountered path.
68       */
69      private final Set<String> path;
70  
71      // ***************************** Initializer ******************************
72  
73      // ***************************** Constructors *****************************
74  
75      private RecursionDetection()
76      {
77        this.path = new LinkedHashSet<String>();
78      }
79  
80      private RecursionDetection(final RecursionDetection parent)
81      {
82        this.path = new LinkedHashSet<String>(parent.path);
83      }
84  
85      // ***************************** Inner Classes ****************************
86  
87      // ******************************** Methods *******************************
88  
89      // --- init ---------------------------------------------------------------
90  
91      // --- get&set ------------------------------------------------------------
92  
93      // --- business -----------------------------------------------------------
94  
95      private void checkRecursion(final String key)
96        throws RecursivePropertyException
97      {
98        if (path.contains(key))
99        {
100         throw new RecursivePropertyException(key, new ArrayList<String>(path));
101       }
102       path.add(key);
103     }
104 
105     // --- object basics ------------------------------------------------------
106 
107     @Override
108     public String toString()
109     {
110       return path.toString();
111     }
112   }
113 
114   // ********************************* Methods ********************************
115 
116   // --- init -----------------------------------------------------------------
117 
118   // --- get&set --------------------------------------------------------------
119 
120   // --- business -------------------------------------------------------------
121 
122   /**
123    * Resolves the expression.
124    *
125    * @param expression the expression to resolve.
126    * @return the requested value or <code>null</code> if there is no value for
127    *         that key.
128    * @throws UnresolvablePropertyException if the property cannot be resolved.
129    * @throws UnknownPropertyException if a referenced property found in the
130    *           expression is not known.
131    * @throws PropertySecurityException if a referenced property found in the
132    *           expression is invalid according to its constraints.
133    */
134   String resolve(final String expression) throws UnresolvablePropertyException,
135     UnknownPropertyException, PropertyValidationException
136   {
137     return resolve(new RecursionDetection(), expression);
138   }
139 
140   private String resolve(final RecursionDetection recursionDetection,
141       final String expression) throws RecursivePropertyException
142   {
143     if (StringUtils.isBlank(expression))
144     {
145       return expression;
146     }
147 
148     final int length = expression.length();
149     final StringBuilder buffer = new StringBuilder(length * 2);
150     int i = 0;
151     while (i < length)
152     {
153       final char c = expression.charAt(i++);
154       if (c == '$' && i < length - 1 && expression.charAt(i) == '{')
155       {
156         final RecursionDetection recursionDetection2 =
157             new RecursionDetection(recursionDetection);
158         i = parsePlaceHolder(recursionDetection2, expression, buffer, i);
159       }
160       else
161       {
162         buffer.append(c);
163       }
164     }
165 
166     return buffer.toString();
167   }
168 
169   private int parsePlaceHolder(final RecursionDetection recursionDetection,
170       final String expression, final StringBuilder buffer, final int index)
171     throws RecursivePropertyException
172   {
173     final int length = expression.length() - 1;
174     int i = index;
175 
176     final StringBuilder keyBuffer = new StringBuilder(64);
177     i = readKey(expression, length, i, keyBuffer);
178     final String key = keyBuffer.toString();
179     recursionDetection.checkRecursion(key);
180     final String replacement = context.get(key);
181     if (replacement == null)
182     {
183       throw new NullValuePropertyException(key, new ArrayList<String>(
184           recursionDetection.path));
185     }
186     final String resolved = resolve(recursionDetection, replacement);
187     buffer.append(resolved);
188     return i + 1;
189   }
190 
191   private int readKey(final String expression, final int length,
192       final int index, final StringBuilder keyBuffer)
193   {
194     int i = index;
195     char n = expression.charAt(++i);
196     while (n != '}' && i < length)
197     {
198       keyBuffer.append(n);
199       n = expression.charAt(++i);
200     }
201     return i;
202   }
203 
204   // --- object basics --------------------------------------------------------
205 
206 }