    001    // Copyright 2006, 2007 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    //     http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.internal.services;
    016
    017    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    018    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newLinkedList;
    019    import org.apache.tapestry5.ioc.services.SymbolProvider;
    020    import org.apache.tapestry5.ioc.services.SymbolSource;
    021
    022    import java.util.LinkedList;
    023    import java.util.List;
    024    import java.util.Map;
    025
    026    public class SymbolSourceImpl implements SymbolSource
    027    {
    028        private final List<SymbolProvider> providers;
    029
    030        /**
    031         * Cache of symbol name to fully expanded symbol value.
    032         */
    033        private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
    034
    035        /**
    036         * Contains execution data needed when performing an expansion (largely, to check for endless recursion).
    037         */
    038        private class SymbolExpansion
    039        {
    040            private final LinkedList<String> expandingSymbols = newLinkedList();
    041
    042            String expandSymbols(String input)
    043            {
    044                StringBuilder builder = null;
    045
    046                int startx = 0;
    047
    048                while (true)
    049                {
    050                    int symbolx = input.indexOf("${", startx);
    051
    052                    // Special case: if the string contains no symbols then return it as is.
    053
    054                    if (startx == 0 && symbolx < 0) return input;
    055
    056                    // The string has at least one symbol, so its OK to create the StringBuilder
    057
    058                    if (builder == null) builder = new StringBuilder();
    059
    060                    // No more symbols found, so add in the rest of the string.
    061
    062                    if (symbolx < 0)
    063                    {
    064                        builder.append(input.substring(startx));
    065                        break;
    066                    }
    067
    068                    builder.append(input.substring(startx, symbolx));
    069
    070                    int endx = input.indexOf("}", symbolx);
    071
    072                    if (endx < 0)
    073                    {
    074                        String message = expandingSymbols.isEmpty() ? ServiceMessages
    075                                .missingSymbolCloseBrace(input) : ServiceMessages
    076                                .missingSymbolCloseBraceInPath(input, path());
    077
    078                        throw new RuntimeException(message);
    079                    }
    080
    081                    String symbolName = input.substring(symbolx + 2, endx);
    082
    083                    builder.append(valueForSymbol(symbolName));
    084
    085                    // Restart the search after the '}'
    086
    087                    startx = endx + 1;
    088                }
    089
    090                return builder.toString();
    091            }
    092
    093            String valueForSymbol(String symbolName)
    094            {
    095                String value = cache.get(symbolName);
    096
    097                if (value == null)
    098                {
    099                    value = expandSymbol(symbolName);
    100
    101                    cache.put(symbolName, value);
    102                }
    103
    104                return value;
    105            }
    106
    107            String expandSymbol(String symbolName)
    108            {
    109                if (expandingSymbols.contains(symbolName))
    110                {
    111                    expandingSymbols.add(symbolName);
    112                    throw new RuntimeException(ServiceMessages.recursiveSymbol(
    113                            symbolName,
    114                            pathFrom(symbolName)));
    115                }
    116
    117                expandingSymbols.addLast(symbolName);
    118
    119                String value = null;
    120
    121                for (SymbolProvider provider : providers)
    122                {
    123                    value = provider.valueForSymbol(symbolName);
    124
    125                    if (value != null) break;
    126                }
    127
    128                if (value == null)
    129                {
    130
    131                    String message = expandingSymbols.size() == 1 ? ServiceMessages
    132                            .symbolUndefined(symbolName) : ServiceMessages.symbolUndefinedInPath(
    133                            symbolName,
    134                            path());
    135
    136                    throw new RuntimeException(message);
    137                }
    138
    139                // The value may have symbols that need expansion.
    140
    141                String result = expandSymbols(value);
    142
    143                // And we're done expanding this symbol
    144
    145                expandingSymbols.removeLast();
    146
    147                return result;
    148
    149            }
    150
    151            String path()
    152            {
    153                StringBuilder builder = new StringBuilder();
    154
    155                boolean first = true;
    156
    157                for (String symbolName : expandingSymbols)
    158                {
    159                    if (!first) builder.append(" --> ");
    160
    161                    builder.append(symbolName);
    162
    163                    first = false;
    164                }
    165
    166                return builder.toString();
    167            }
    168
    169            String pathFrom(String startSymbolName)
    170            {
    171                StringBuilder builder = new StringBuilder();
    172
    173                boolean first = true;
    174                boolean match = false;
    175
    176                for (String symbolName : expandingSymbols)
    177                {
    178                    if (!match)
    179                    {
    180                        if (symbolName.equals(startSymbolName))
    181                            match = true;
    182                        else
    183                            continue;
    184                    }
    185
    186                    if (!first) builder.append(" --> ");
    187
    188                    builder.append(symbolName);
    189
    190                    first = false;
    191                }
    192
    193                return builder.toString();
    194            }
    195        }
    196
    197        public SymbolSourceImpl(final List<SymbolProvider> providers)
    198        {
    199            this.providers = providers;
    200        }
    201
    202        public String expandSymbols(String input)
    203        {
    204            return new SymbolExpansion().expandSymbols(input);
    205        }
    206
    207        public String valueForSymbol(String symbolName)
    208        {
    209            String value = cache.get(symbolName);
    210
    211            // If already in the cache, then return it. Otherwise, let the SE find the value and
    212            // update the cache.
    213
    214            return value != null ? value : new SymbolExpansion().valueForSymbol(symbolName);
    215        }
    216
    217    }