001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.resource;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.nio.charset.Charset;
022import java.util.Locale;
023import java.util.regex.Pattern;
024
025import org.apache.wicket.WicketRuntimeException;
026import org.apache.wicket.request.Url;
027import org.apache.wicket.request.resource.ResourceReference;
028import org.apache.wicket.util.io.IOUtils;
029import org.apache.wicket.util.lang.Args;
030import org.apache.wicket.util.resource.IResourceStream;
031import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
032import org.apache.wicket.util.string.Strings;
033
034/**
035 * Utilities for resources.
036 * 
037 * @author Jeremy Thomerson
038 */
039public class ResourceUtil
040{
041
042        private static final Pattern ESCAPED_ATTRIBUTE_PATTERN = Pattern.compile("(\\w)~(\\w)");
043
044        /**
045         * Reads resource reference attributes (style, locale, variation) encoded in the given string.
046         * 
047         * @param encodedAttributes
048         *                      the string containing the resource attributes
049         * @return the encoded attributes
050         * 
051         * @see ResourceReference.UrlAttributes
052         */
053        public static ResourceReference.UrlAttributes decodeResourceReferenceAttributes(String encodedAttributes)
054        {
055                Locale locale = null;
056                String style = null;
057                String variation = null;
058
059                if (Strings.isEmpty(encodedAttributes) == false)
060                {
061                        String split[] = Strings.split(encodedAttributes, '-');
062                        locale = parseLocale(split[0]);
063                        if (split.length == 2)
064                        {
065                                style = Strings.defaultIfEmpty(unescapeAttributesSeparator(split[1]), null);
066                        }
067                        else if (split.length == 3)
068                        {
069                                style = Strings.defaultIfEmpty(unescapeAttributesSeparator(split[1]), null);
070                                variation = Strings.defaultIfEmpty(unescapeAttributesSeparator(split[2]), null);
071                        }
072                }
073                return new ResourceReference.UrlAttributes(locale, style, variation);
074        }
075        
076        /**
077         * Reads resource reference attributes (style, locale, variation) encoded in the given URL. 
078         * 
079         * @param url
080         *                      the url containing the resource attributes
081         * @return the encoded attributes
082         * 
083         * @see ResourceReference.UrlAttributes
084         */
085        public static ResourceReference.UrlAttributes decodeResourceReferenceAttributes(Url url)
086        {
087                Args.notNull(url, "url");
088        
089                if (url.getQueryParameters().size() > 0)
090                {
091                        Url.QueryParameter param = url.getQueryParameters().get(0);
092                        if (Strings.isEmpty(param.getValue()))
093                        {
094                                return decodeResourceReferenceAttributes(param.getName());
095                        }
096                }
097                return new ResourceReference.UrlAttributes(null, null, null);
098        }
099
100        /**
101         * Encodes the given resource reference attributes returning the corresponding textual representation.
102         * 
103         * @param attributes
104         *              the resource reference attributes to encode
105         * @return the textual representation for the given attributes
106         * 
107         * @see ResourceReference.UrlAttributes
108         */
109        public static String encodeResourceReferenceAttributes(ResourceReference.UrlAttributes attributes)
110        {
111                if (attributes == null ||
112                        (attributes.getLocale() == null && attributes.getStyle() == null && attributes.getVariation() == null))
113                {
114                        return null;
115                }
116                else
117                {
118                        StringBuilder res = new StringBuilder(32);
119                        if (attributes.getLocale() != null)
120                        {
121                                res.append(attributes.getLocale());
122                        }
123                        boolean styleEmpty = Strings.isEmpty(attributes.getStyle());
124                        if (!styleEmpty)
125                        {
126                                res.append('-');
127                                res.append(escapeAttributesSeparator(attributes.getStyle()));
128                        }
129                        if (!Strings.isEmpty(attributes.getVariation()))
130                        {
131                                if (styleEmpty)
132                                {
133                                        res.append("--");
134                                }
135                                else
136                                {
137                                        res.append('-');
138                                }
139                                res.append(escapeAttributesSeparator(attributes.getVariation()));
140                        }
141                        return res.toString();
142                }
143        }
144
145        /**
146         * Encodes the attributes of the given resource reference in the specified url.
147         * 
148         * @param url
149         *                      the resource reference attributes to encode
150         * @param reference
151         * 
152         * @see ResourceReference.UrlAttributes
153         * @see Url
154         */
155        public static void encodeResourceReferenceAttributes(Url url, ResourceReference reference)
156        {
157                Args.notNull(url, "url");
158                Args.notNull(reference, "reference");
159
160                String encoded = encodeResourceReferenceAttributes(reference.getUrlAttributes());
161                if (!Strings.isEmpty(encoded))
162                {
163                        url.getQueryParameters().add(new Url.QueryParameter(encoded, ""));
164                }
165        }
166
167        /**
168         * Escapes any occurrences of <em>-</em> character in the style and variation
169         * attributes with <em>~</em>. Any occurrence of <em>~</em> is encoded as <em>~~</em>.
170         *
171         * @param attribute
172         *      the attribute to escape
173         * @return the attribute with escaped separator character
174         */
175        public static CharSequence escapeAttributesSeparator(String attribute)
176        {
177                CharSequence tmp = Strings.replaceAll(attribute, "~", "~~");
178                return Strings.replaceAll(tmp, "-", "~");
179        }
180
181        /**
182         * Parses the string representation of a {@link java.util.Locale} (for example 'en_GB').
183         * 
184         * @param locale
185         *              the string representation of a {@link java.util.Locale}
186         * @return the corresponding {@link java.util.Locale} instance
187         */
188        public static Locale parseLocale(String locale)
189        {
190                if (Strings.isEmpty(locale))
191                {
192                        return null;
193                }
194                else
195                {
196                        String parts[] = locale.toLowerCase(Locale.ROOT).split("_", 3);
197                        if (parts.length == 1)
198                        {
199                                return new Locale(parts[0]);
200                        }
201                        else if (parts.length == 2)
202                        {
203                                return new Locale(parts[0], parts[1]);
204                        }
205                        else if (parts.length == 3)
206                        {
207                                return new Locale(parts[0], parts[1], parts[2]);
208                        }
209                        else
210                        {
211                                return null;
212                        }
213                }
214        }
215
216        /**
217         * read string with platform default encoding from resource stream
218         * 
219         * @param resourceStream
220         * @return string read from resource stream
221         * 
222         * @see #readString(org.apache.wicket.util.resource.IResourceStream, java.nio.charset.Charset)
223         */
224        public static String readString(IResourceStream resourceStream)
225        {
226                return readString(resourceStream, null);
227        }
228
229        /**
230         * read string with specified encoding from resource stream
231         * 
232         * @param resourceStream
233         *            string source
234         * @param charset
235         *            charset for the string encoding (use <code>null</code> for platform default)
236         * @return string read from resource stream
237         */
238        public static String readString(IResourceStream resourceStream, Charset charset)
239        {
240                try
241                {
242                        InputStream stream = resourceStream.getInputStream();
243
244                        try
245                        {
246                                byte[] bytes = IOUtils.toByteArray(stream);
247
248                                if (charset == null)
249                                {
250                                        charset = Charset.defaultCharset();
251                                }
252
253                                return new String(bytes, charset.name());
254                        }
255                        finally
256                        {
257                                resourceStream.close();
258                        }
259                }
260                catch (IOException e)
261                {
262                        throw new WicketRuntimeException("failed to read string from " + resourceStream, e);
263                }
264                catch (ResourceStreamNotFoundException e)
265                {
266                        throw new WicketRuntimeException("failed to locate stream from " + resourceStream, e);
267                }
268        }
269
270        /**
271         * Reverts the escaping applied by {@linkplain #escapeAttributesSeparator(String)} - unescapes
272         * occurrences of <em>~</em> character in the style and variation attributes with <em>-</em>.
273         *
274         * @param attribute
275         *      the attribute to unescape
276         * @return the attribute with escaped separator character
277         */
278        public static String unescapeAttributesSeparator(String attribute)
279        {
280                String tmp = ESCAPED_ATTRIBUTE_PATTERN.matcher(attribute).replaceAll("$1-$2");
281                return Strings.replaceAll(tmp, "~~", "~").toString();
282        }
283
284        private ResourceUtil()
285        {
286                // no-op
287        }
288}