    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
    015package org.apache.tapestry5.ioc.internal.services;
    016
    017import org.apache.tapestry5.commons.util.CollectionFactory;
    018import org.apache.tapestry5.ioc.services.SymbolProvider;
    019import org.apache.tapestry5.ioc.services.SymbolSource;
    020
    021import static org.apache.tapestry5.commons.util.CollectionFactory.newLinkedList;
    022
    023import java.util.LinkedList;
    024import java.util.List;
    025import java.util.Map;
    026
    027public class SymbolSourceImpl implements SymbolSource
    028{
    029    private final List<SymbolProvider> providers;
    030
    031    /**
    032     * Cache of symbol name to fully expanded symbol value.
    033     */
    034    private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
    035
    036    /**
    037     * Contains execution data needed when performing an expansion (largely, to check for endless recursion).
    038     */
    039    private class SymbolExpansion
    040    {
    041        private final LinkedList<String> expandingSymbols = newLinkedList();
    042
    043        String expandSymbols(String input)
    044        {
    045            StringBuilder builder = null;
    046
    047            int startx = 0;
    048
    049            while (true)
    050            {
    051                int symbolx = input.indexOf("${", startx);
    052
    053                // Special case: if the string contains no symbols then return it as is.
    054
    055                if (startx == 0 && symbolx < 0) return input;
    056
    057                // The string has at least one symbol, so its OK to create the StringBuilder
    058
    059                if (builder == null) builder = new StringBuilder();
    060
    061                // No more symbols found, so add in the rest of the string.
    062
    063                if (symbolx < 0)
    064                {
    065                    builder.append(input.substring(startx));
    066                    break;
    067                }
    068
    069                builder.append(input.substring(startx, symbolx));
    070
    071                int endx = input.indexOf("}", symbolx);
    072
    073                if (endx < 0)
    074                {
    075                    String message = expandingSymbols.isEmpty()
    076                            ? String.format("Input string '%s' is missing a symbol closing brace.", input)
    077                            : String.format("Input string '%s' is missing a symbol closing brace (in %s).", input, path());
    078
    079                    throw new RuntimeException(message);
    080                }
    081
    082                String symbolName = input.substring(symbolx + 2, endx);
    083
    084                builder.append(valueForSymbol(symbolName));
    085
    086                // Restart the search after the '}'
    087
    088                startx = endx + 1;
    089            }
    090
    091            return builder.toString();
    092        }
    093
    094        String valueForSymbol(String symbolName)
    095        {
    096            String value = cache.get(symbolName);
    097
    098            if (value == null)
    099            {
    100                value = expandSymbol(symbolName);
    101
    102                cache.put(symbolName, value);
    103            }
    104
    105            return value;
    106        }
    107
    108        String expandSymbol(String symbolName)
    109        {
    110            if (expandingSymbols.contains(symbolName))
    111            {
    112                expandingSymbols.add(symbolName);
    113                throw new RuntimeException(String.format("Symbol '%s' is defined in terms of itself (%s).",
    114                        symbolName,
    115                        pathFrom(symbolName)));
    116            }
    117
    118            expandingSymbols.addLast(symbolName);
    119
    120            String value = null;
    121
    122            for (SymbolProvider provider : providers)
    123            {
    124                value = provider.valueForSymbol(symbolName);
    125
    126                if (value != null) break;
    127            }
    128
    129            if (value == null)
    130            {
    131
    132                String message = expandingSymbols.size() == 1
    133                        ? String.format("Symbol '%s' is not defined.", symbolName)
    134                        : String.format("Symbol '%s' is not defined (in %s).", symbolName, 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    @Override
    203    public String expandSymbols(String input)
    204    {
    205        return new SymbolExpansion().expandSymbols(input);
    206    }
    207
    208    @Override
    209    public String valueForSymbol(String symbolName)
    210    {
    211        String value = cache.get(symbolName);
    212
    213        // If already in the cache, then return it. Otherwise, let the SE find the value and
    214        // update the cache.
    215
    216        return value != null ? value : new SymbolExpansion().valueForSymbol(symbolName);
    217    }
    218
    219}