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                    }
159    
160                    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("});");
175                    }
176    
177                    writer.write("\n// ]]>\n</script>");
178            }
179    
180            public static enum ModulesType {
181    
182                    AUI, ES6
183    
184            }
185    
186            private void _addToSBIndexList(StringBundler sb) {
187                    ObjectValuePair<StringBundler, Integer> ovp = new ObjectValuePair<>(
188                            sb, sb.index());
189    
190                    int index = _sbIndexList.indexOf(ovp);
191    
192                    if (index == -1) {
193                            _sbIndexList.add(ovp);
194                    }
195                    else {
196                            ovp = _sbIndexList.get(index);
197    
198                            ovp.setValue(sb.index());
199                    }
200            }
201    
202            private Map<String, String> _generateVariables(
203                    Set<String> requiredFileNames) {
204    
205                    Map<String, Integer> indexes = new HashMap<>();
206                    Set<String> generatedVariables = new HashSet<>();
207                    Map<String, String> generatedVariablesMap = new HashMap<>();
208    
209                    for (String requiredFileName : requiredFileNames) {
210                            StringBundler sb = new StringBundler();
211    
212                            CharSequence firstCharSequence = requiredFileName.subSequence(0, 1);
213    
214                            Matcher matcher = _validFirstCharacterPattern.matcher(
215                                    firstCharSequence);
216    
217                            if (!matcher.matches()) {
218                                    sb.append(StringPool.UNDERLINE);
219                            }
220                            else {
221                                    sb.append(firstCharSequence);
222                            }
223    
224                            for (int i = 1; i < requiredFileName.length(); i++) {
225                                    CharSequence currentCharSequence = requiredFileName.subSequence(
226                                            i, i + 1);
227    
228                                    matcher = _validCharactersPattern.matcher(currentCharSequence);
229    
230                                    if (!matcher.matches()) {
231                                            while (++i < requiredFileName.length()) {
232                                                    CharSequence nextCharSequence =
233                                                            requiredFileName.subSequence(i, i + 1);
234    
235                                                    matcher = _validCharactersPattern.matcher(
236                                                            nextCharSequence);
237    
238                                                    if (matcher.matches()) {
239                                                            sb.append(
240                                                                    StringUtil.toUpperCase(
241                                                                            nextCharSequence.toString()));
242    
243                                                            break;
244                                                    }
245                                            }
246                                    }
247                                    else {
248                                            sb.append(currentCharSequence);
249                                    }
250                            }
251    
252                            String generatedVariable = sb.toString();
253    
254                            if (generatedVariables.contains(generatedVariable)) {
255                                    int index = 1;
256    
257                                    if (indexes.containsKey(generatedVariable)) {
258                                            index = indexes.get(generatedVariable) + 1;
259                                    }
260    
261                                    indexes.put(generatedVariable, index);
262    
263                                    generatedVariable += index;
264                            }
265    
266                            generatedVariables.add(generatedVariable);
267                            generatedVariablesMap.put(requiredFileName, generatedVariable);
268                    }
269    
270                    return generatedVariablesMap;
271            }
272    
273            private PortletData _getPortletData(String portletId) {
274                    if (Validator.isNull(portletId)) {
275                            portletId = StringPool.BLANK;
276                    }
277    
278                    PortletData portletData = _portletDataMap.get(portletId);
279    
280                    if (portletData == null) {
281                            portletData = new PortletData();
282    
283                            PortletData oldPortletData = _portletDataMap.putIfAbsent(
284                                    portletId, portletData);
285    
286                            if (oldPortletData != null) {
287                                    portletData = oldPortletData;
288                            }
289                    }
290    
291                    return portletData;
292            }
293    
294            private static final Pattern _validCharactersPattern = Pattern.compile(
295                    "[0-9a-z_$]", Pattern.CASE_INSENSITIVE);
296            private static final Pattern _validFirstCharacterPattern = Pattern.compile(
297                    "[a-z_$]", Pattern.CASE_INSENSITIVE);
298            private static final long serialVersionUID = 1L;
299    
300            private final ConcurrentMap<String, PortletData> _portletDataMap =
301                    new ConcurrentHashMap<>();
302            private final List<ObjectValuePair<StringBundler, Integer>> _sbIndexList =
303                    new ArrayList<>();
304    
305            private static class PortletData implements Serializable {
306    
307                    public void append(
308                            String content, String modules, ModulesType modulesType) {
309    
310                            if (Validator.isNull(modules)) {
311                                    _rawSB.append(content);
312                            }
313                            else {
314                                    String[] modulesArray = StringUtil.split(modules);
315    
316                                    if (modulesType == ModulesType.AUI) {
317                                            _auiCallbackSB.append("(function() {");
318                                            _auiCallbackSB.append(content);
319                                            _auiCallbackSB.append("})();");
320    
321                                            for (String module : modulesArray) {
322                                                    _auiModulesSet.add(StringUtil.trim(module));
323                                            }
324                                    }
325                                    else {
326                                            _es6CallbackSB.append("(function() {");
327                                            _es6CallbackSB.append(content);
328                                            _es6CallbackSB.append("})();");
329    
330                                            for (String module : modulesArray) {
331                                                    _es6ModulesSet.add(StringUtil.trim(module));
332                                            }
333                                    }
334                            }
335                    }
336    
337                    public void append(
338                            StringBundler contentSB, String modules, ModulesType modulesType) {
339    
340                            if (Validator.isNull(modules)) {
341                                    _rawSB.append(contentSB);
342                            }
343                            else {
344                                    String[] modulesArray = StringUtil.split(modules);
345    
346                                    if (modulesType == ModulesType.AUI) {
347                                            _auiCallbackSB.append("(function() {");
348                                            _auiCallbackSB.append(contentSB);
349                                            _auiCallbackSB.append("})();");
350    
351                                            for (String module : modulesArray) {
352                                                    _auiModulesSet.add(StringUtil.trim(module));
353                                            }
354                                    }
355                                    else {
356                                            _es6CallbackSB.append("(function() {");
357                                            _es6CallbackSB.append(contentSB);
358                                            _es6CallbackSB.append("})();");
359    
360                                            for (String module : modulesArray) {
361                                                    _es6ModulesSet.add(StringUtil.trim(module));
362                                            }
363                                    }
364                            }
365                    }
366    
367                    private static final long serialVersionUID = 1L;
368    
369                    private final StringBundler _auiCallbackSB = new StringBundler();
370                    private final Set<String> _auiModulesSet = new HashSet<>();
371                    private final StringBundler _es6CallbackSB = new StringBundler();
372                    private final Set<String> _es6ModulesSet = new HashSet<>();
373                    private final StringBundler _rawSB = new StringBundler();
374    
375            }
376    
377    }