/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.resource.loader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.settings.ResourceSettings;
import org.apache.wicket.util.string.AppendingStringBuffer;
/**
* Creates a nested string resource loader which resolves nested keys.<br>
* <br>
* Example:
*
* <pre>
* <code>
* List<IStringResourceLoader> loaders = getResourceSettings().getStringResourceLoaders();
* // Add more loaders here
* NestedStringResourceLoader element = new NestedStringResourceLoader(loaders,Pattern.compile("#\\(([^ ]*?)\\)"));
* loaders.clear();
* loaders.add(element);
* </code>
* </pre>
*
* @author Sven Meier
* @author Tobias Soloschenko
*
*/
public class NestedStringResourceLoader implements IStringResourceLoader
{
private final Pattern pattern;
private final List<IStringResourceLoader> loaders;
private final ResourceSettings resourceSettings;
/**
* Creates a nested string resource loader
*
* @param loaders
* the loaders to be added in a chain
* @param pattern
* the pattern for nested keys. Example for <b>#(key)</b> is the pattern:
* <b>Pattern.compile("#\\(([^ ]*?)\\)");</b>
*/
public NestedStringResourceLoader(List<IStringResourceLoader> loaders, Pattern pattern)
{
this.loaders = new ArrayList<>(loaders);
this.pattern = pattern;
this.resourceSettings = Application.get().getResourceSettings();
}
@Override
public String loadStringResource(Component component, String key, Locale locale, String style,
String variation)
{
return loadNestedStringResource(component, key, locale, style, variation);
}
@Override
public String loadStringResource(Class<?> clazz, String key, Locale locale, String style,
String variation)
{
return loadNestedStringResource(clazz, key, locale, style, variation);
}
/**
* loads nested string resources
*
* @param scope
* the scope to find the key
* @param key
* the actual key
* @param locale
* the locale
* @param style
* the style
* @param variation
* the variation
* @return the load string
*/
private String loadNestedStringResource(Object scope, String key, Locale locale, String style,
String variation)
{
Class<?> clazz = null;
Component component = null;
if (scope instanceof Component)
{
component = (Component)scope;
}
else
{
clazz = (Class<?>)scope;
}
Iterator<IStringResourceLoader> iter = loaders.iterator();
String value = null;
while (iter.hasNext() && (value == null))
{
IStringResourceLoader loader = iter.next();
value = component != null
? loader.loadStringResource(component, key, locale, style, variation)
: loader.loadStringResource(clazz, key, locale, style, variation);
}
if (value == null)
{
return handleMissingKey(key, locale, style, component, value);
}
StringBuffer output = new StringBuffer();
Matcher matcher = pattern.matcher(value);
// Search for other nested keys to replace
while (matcher.find())
{
String nestedKey = matcher.group(1);
String replacedPlaceHolder = component != null
? loadNestedStringResource(component, nestedKey, locale, style, variation)
: loadNestedStringResource(clazz, nestedKey, locale, style, variation);
replacedPlaceHolder = handleMissingKey(nestedKey, locale, style, component,
replacedPlaceHolder);
matcher.appendReplacement(output, replacedPlaceHolder);
}
matcher.appendTail(output);
return output.toString();
}
/**
* Handles a missing key
*
* @param nestedKey
* the key which is going to be handled
* @param locale
* the actual locale
* @param style
* the style
* @param component
* the component
*
* @param replacedPlaceHolder
* @return the replacedPlaceholder
*/
private String handleMissingKey(String nestedKey, Locale locale, String style,
Component component, String replacedPlaceHolder)
{
if (replacedPlaceHolder == null)
{
if (resourceSettings.getThrowExceptionOnMissingResource())
{
AppendingStringBuffer message = new AppendingStringBuffer(
"Unable to find property: '");
message.append(nestedKey);
message.append('\'');
if (component != null)
{
message.append(" for component: ");
message.append(component.getPageRelativePath());
message.append(" [class=").append(component.getClass().getName()).append(']');
}
message.append(". Locale: ").append(locale).append(", style: ").append(style);
throw new MissingResourceException(message.toString(),
(component != null ? component.getClass().getName() : ""), nestedKey);
}
else
{
replacedPlaceHolder = "[Warning: Property for '" + nestedKey + "' not found]";
}
}
return replacedPlaceHolder;
}
}