001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.servlet.taglib.aui;
016    
017    import com.liferay.portal.kernel.util.Mergeable;
018    import com.liferay.portal.kernel.util.ObjectValuePair;
019    import com.liferay.portal.kernel.util.StringBundler;
020    import com.liferay.portal.kernel.util.StringPool;
021    import com.liferay.portal.kernel.util.StringUtil;
022    import com.liferay.portal.kernel.util.Validator;
023    
024    import java.io.IOException;
025    import java.io.Serializable;
026    import java.io.Writer;
027    
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Set;
035    import java.util.concurrent.ConcurrentHashMap;
036    import java.util.concurrent.ConcurrentMap;
037    import java.util.regex.Matcher;
038    import java.util.regex.Pattern;
039    
040    import javax.servlet.http.HttpServletRequest;
041    
042    /**
043     * @author Brian Wing Shun Chan
044     * @author Shuyang Zhou
045     */
046    public class ScriptData implements Mergeable<ScriptData>, Serializable {
047    
048            public void append(
049                    String portletId, String content, String modules,
050                    ModulesType modulesType) {
051    
052                    PortletData portletData = _getPortletData(portletId);
053    
054                    portletData.append(content, modules, modulesType);
055            }
056    
057            public void append(
058                    String portletId, StringBundler contentSB, String modules,
059                    ModulesType modulesType) {
060    
061                    PortletData portletData = _getPortletData(portletId);
062    
063                    portletData.append(contentSB, modules, modulesType);
064            }
065    
066            public void mark() {
067                    for (PortletData portletData : _portletDataMap.values()) {
068                            _addToSBIndexList(portletData._auiCallbackSB);
069                            _addToSBIndexList(portletData._es6CallbackSB);
070                            _addToSBIndexList(portletData._rawSB);
071                    }
072            }
073    
074            @Override
075            public ScriptData merge(ScriptData scriptData) {
076                    if ((scriptData != null) && (scriptData != this)) {
077                            _portletDataMap.putAll(scriptData._portletDataMap);
078                    }
079    
080                    return this;
081            }
082    
083            public void reset() {
084                    for (ObjectValuePair<StringBundler, Integer> ovp : _sbIndexList) {
085                            StringBundler sb = ovp.getKey();
086    
087                            sb.setIndex(ovp.getValue());
088                    }
089            }
090    
091            public void writeTo(HttpServletRequest request, Writer writer)
092                    throws IOException {
093    
094                    writer.write("<script type=\"text/javascript\">\n// <![CDATA[\n");
095    
096                    StringBundler auiModulesSB = new StringBundler(_portletDataMap.size());
097                    Set<String> auiModulesSet = new HashSet<>();
098                    StringBundler es6ModulesSB = new StringBundler(_portletDataMap.size());
099                    Set<String> es6ModulesSet = new HashSet<>();
100    
101                    for (PortletData portletData : _portletDataMap.values()) {
102                            portletData._rawSB.writeTo(writer);
103    
104                            if (!portletData._auiModulesSet.isEmpty()) {
105                                    auiModulesSB.append(portletData._auiCallbackSB);
106                            }
107    
108                            if (!portletData._es6ModulesSet.isEmpty()) {
109                                    es6ModulesSB.append(portletData._es6CallbackSB);
110                            }
111    
112                            auiModulesSet.addAll(portletData._auiModulesSet);
113                            es6ModulesSet.addAll(portletData._es6ModulesSet);
114                    }
115    
116                    if ((auiModulesSB.index() == 0) && (es6ModulesSB.index() == 0)) {
117                            writer.write("\n// ]]>\n</script>");
118    
119                            return;
120                    }
121    
122                    if (!es6ModulesSet.isEmpty()) {
123                            writer.write("require(");
124    
125                            Map<String, String> generatedVariables = _generateVariables(
126                                    es6ModulesSet);
127    
128                            Iterator<String> iterator = es6ModulesSet.iterator();
129    
130                            while (iterator.hasNext()) {
131                                    writer.write(StringPool.APOSTROPHE);
132                                    writer.write(iterator.next());
133                                    writer.write(StringPool.APOSTROPHE);
134    
135                                    if (iterator.hasNext()) {
136                                            writer.write(StringPool.COMMA_AND_SPACE);
137                                    }
138                            }
139    
140                            writer.write(StringPool.COMMA_AND_SPACE);
141                            writer.write("function(");
142    
143                            iterator = es6ModulesSet.iterator();
144    
145                            while (iterator.hasNext()) {
146                                    writer.write(generatedVariables.get(iterator.next()));
147    
148                                    if (iterator.hasNext()) {
149                                            writer.write(StringPool.COMMA_AND_SPACE);
150                                    }
151                            }
152    
153                            writer.write(") {\n");
154    
155                            es6ModulesSB.writeTo(writer);
156    
157                            writer.write("},\nfunction(error) {\nconsole.error(error);\n});");
158                            writer.write("\n// ]]>\n</script>");
159                    }
160                    else if (!auiModulesSet.isEmpty()) {
161                            writer.write("AUI().use(");
162    
163                            for (String use : auiModulesSet) {
164                                    writer.write(StringPool.APOSTROPHE);
165                                    writer.write(use);
166                                    writer.write(StringPool.APOSTROPHE);
167                                    writer.write(StringPool.COMMA_AND_SPACE);
168                            }
169    
170                            writer.write("function(A) {");
171    
172                            auiModulesSB.writeTo(writer);
173    
174                            writer.write("});\n// ]]>\n</script>");
175                    }
176            }
177    
178            public static enum ModulesType {
179    
180                    AUI, ES6
181    
182            }
183    
184            private void _addToSBIndexList(StringBundler sb) {
185                    ObjectValuePair<StringBundler, Integer> ovp = new ObjectValuePair<>(
186                            sb, sb.index());
187    
188                    int index = _sbIndexList.indexOf(ovp);
189    
190                    if (index == -1) {
191                            _sbIndexList.add(ovp);
192                    }
193                    else {
194                            ovp = _sbIndexList.get(index);
195    
196                            ovp.setValue(sb.index());
197                    }
198            }
199    
200            private Map<String, String> _generateVariables(
201                    Set<String> requiredFileNames) {
202    
203                    Map<String, Integer> indexes = new HashMap<>();
204                    Set<String> generatedVariables = new HashSet<>();
205                    Map<String, String> generatedVariablesMap = new HashMap<>();
206    
207                    for (String requiredFileName : requiredFileNames) {
208                            StringBundler sb = new StringBundler();
209    
210                            CharSequence firstCharSequence = requiredFileName.subSequence(0, 1);
211    
212                            Matcher matcher = _validFirstCharacterPattern.matcher(
213                                    firstCharSequence);
214    
215                            if (!matcher.matches()) {
216                                    sb.append(StringPool.UNDERLINE);
217                            }
218                            else {
219                                    sb.append(firstCharSequence);
220                            }
221    
222                            for (int i = 1; i < requiredFileName.length(); i++) {
223                                    CharSequence currentCharSequence = requiredFileName.subSequence(
224                                            i, i + 1);
225    
226                                    matcher = _validCharactersPattern.matcher(currentCharSequence);
227    
228                                    if (!matcher.matches()) {
229                                            while (++i < requiredFileName.length()) {
230                                                    CharSequence nextCharSequence =
231                                                            requiredFileName.subSequence(i, i + 1);
232    
233                                                    matcher = _validCharactersPattern.matcher(
234                                                            nextCharSequence);
235    
236                                                    if (matcher.matches()) {
237                                                            sb.append(
238                                                                    StringUtil.toUpperCase(
239                                                                            nextCharSequence.toString()));
240    
241                                                            break;
242                                                    }
243                                            }
244                                    }
245                                    else {
246                                            sb.append(currentCharSequence);
247                                    }
248                            }
249    
250                            String generatedVariable = sb.toString();
251    
252                            if (generatedVariables.contains(generatedVariable)) {
253                                    int index = 1;
254    
255                                    if (indexes.containsKey(generatedVariable)) {
256                                            index = indexes.get(generatedVariable) + 1;
257                                    }
258    
259                                    indexes.put(generatedVariable, index);
260    
261                                    generatedVariable += index;
262                            }
263    
264                            generatedVariables.add(generatedVariable);
265                            generatedVariablesMap.put(requiredFileName, generatedVariable);
266                    }
267    
268                    return generatedVariablesMap;
269            }
270    
271            private PortletData _getPortletData(String portletId) {
272                    if (Validator.isNull(portletId)) {
273                            portletId = StringPool.BLANK;
274                    }
275    
276                    PortletData portletData = _portletDataMap.get(portletId);
277    
278                    if (portletData == null) {
279                            portletData = new PortletData();
280    
281                            PortletData oldPortletData = _portletDataMap.putIfAbsent(
282                                    portletId, portletData);
283    
284                            if (oldPortletData != null) {
285                                    portletData = oldPortletData;
286                            }
287                    }
288    
289                    return portletData;
290            }
291    
292            private static final Pattern _validCharactersPattern = Pattern.compile(
293                    "[0-9a-z_$]", Pattern.CASE_INSENSITIVE);
294            private static final Pattern _validFirstCharacterPattern = Pattern.compile(
295                    "[a-z_$]", Pattern.CASE_INSENSITIVE);
296            private static final long serialVersionUID = 1L;
297    
298            private final ConcurrentMap<String, PortletData> _portletDataMap =
299                    new ConcurrentHashMap<>();
300            private final List<ObjectValuePair<StringBundler, Integer>> _sbIndexList =
301                    new ArrayList<>();
302    
303            private static class PortletData implements Serializable {
304    
305                    public void append(
306                            String content, String modules, ModulesType modulesType) {
307    
308                            if (Validator.isNull(modules)) {
309                                    _rawSB.append(content);
310                            }
311                            else {
312                                    String[] modulesArray = StringUtil.split(modules);
313    
314                                    if (modulesType == ModulesType.AUI) {
315                                            _auiCallbackSB.append("(function() {");
316                                            _auiCallbackSB.append(content);
317                                            _auiCallbackSB.append("})();");
318    
319                                            for (String module : modulesArray) {
320                                                    _auiModulesSet.add(StringUtil.trim(module));
321                                            }
322                                    }
323                                    else {
324                                            _es6CallbackSB.append("(function() {");
325                                            _es6CallbackSB.append(content);
326                                            _es6CallbackSB.append("})();");
327    
328                                            for (String module : modulesArray) {
329                                                    _es6ModulesSet.add(StringUtil.trim(module));
330                                            }
331                                    }
332                            }
333                    }
334    
335                    public void append(
336                            StringBundler contentSB, String modules, ModulesType modulesType) {
337    
338                            if (Validator.isNull(modules)) {
339                                    _rawSB.append(contentSB);
340                            }
341                            else {
342                                    String[] modulesArray = StringUtil.split(modules);
343    
344                                    if (modulesType == ModulesType.AUI) {
345                                            _auiCallbackSB.append("(function() {");
346                                            _auiCallbackSB.append(contentSB);
347                                            _auiCallbackSB.append("})();");
348    
349                                            for (String module : modulesArray) {
350                                                    _auiModulesSet.add(StringUtil.trim(module));
351                                            }
352                                    }
353                                    else {
354                                            _es6CallbackSB.append("(function() {");
355                                            _es6CallbackSB.append(contentSB);
356                                            _es6CallbackSB.append("})();");
357    
358                                            for (String module : modulesArray) {
359                                                    _es6ModulesSet.add(StringUtil.trim(module));
360                                            }
361                                    }
362                            }
363                    }
364    
365                    private static final long serialVersionUID = 1L;
366    
367                    private final StringBundler _auiCallbackSB = new StringBundler();
368                    private final Set<String> _auiModulesSet = new HashSet<>();
369                    private final StringBundler _es6CallbackSB = new StringBundler();
370                    private final Set<String> _es6ModulesSet = new HashSet<>();
371                    private final StringBundler _rawSB = new StringBundler();
372    
373            }
374    
375    }