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.CharPool;
018    import com.liferay.portal.kernel.util.Mergeable;
019    import com.liferay.portal.kernel.util.ObjectValuePair;
020    import com.liferay.portal.kernel.util.StringBundler;
021    import com.liferay.portal.kernel.util.StringPool;
022    import com.liferay.portal.kernel.util.StringUtil;
023    import com.liferay.portal.kernel.util.Validator;
024    
025    import java.io.IOException;
026    import java.io.Serializable;
027    import java.io.Writer;
028    
029    import java.util.ArrayList;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.Set;
034    import java.util.concurrent.ConcurrentHashMap;
035    import java.util.concurrent.ConcurrentMap;
036    
037    /**
038     * @author Brian Wing Shun Chan
039     * @author Shuyang Zhou
040     */
041    public class ScriptData implements Mergeable<ScriptData>, Serializable {
042    
043            public void append(
044                    String portletId, String content, String modules,
045                    ModulesType modulesType) {
046    
047                    PortletData portletData = _getPortletData(portletId);
048    
049                    portletData.append(content, modules, modulesType);
050            }
051    
052            public void append(
053                    String portletId, StringBundler contentSB, String modules,
054                    ModulesType modulesType) {
055    
056                    PortletData portletData = _getPortletData(portletId);
057    
058                    portletData.append(contentSB, modules, modulesType);
059            }
060    
061            public void mark() {
062                    for (PortletData portletData : _portletDataMap.values()) {
063                            _addToSBIndexList(portletData._auiCallbackSB);
064                            _addToSBIndexList(portletData._es6CallbackSB);
065                            _addToSBIndexList(portletData._rawSB);
066                    }
067            }
068    
069            @Override
070            public ScriptData merge(ScriptData scriptData) {
071                    if ((scriptData != null) && (scriptData != this)) {
072                            _portletDataMap.putAll(scriptData._portletDataMap);
073                    }
074    
075                    return this;
076            }
077    
078            public void reset() {
079                    for (ObjectValuePair<StringBundler, Integer> ovp : _sbIndexList) {
080                            StringBundler sb = ovp.getKey();
081    
082                            sb.setIndex(ovp.getValue());
083                    }
084            }
085    
086            @Override
087            public ScriptData split() {
088                    return new ScriptData();
089            }
090    
091            public void writeTo(Writer writer) throws IOException {
092                    writer.write("<script type=\"text/javascript\">\n// <![CDATA[\n");
093    
094                    StringBundler auiModulesSB = new StringBundler(_portletDataMap.size());
095                    Set<String> auiModulesSet = new HashSet<>();
096                    StringBundler es6ModulesSB = new StringBundler(_portletDataMap.size());
097                    Set<String> es6ModulesSet = new HashSet<>();
098    
099                    for (PortletData portletData : _portletDataMap.values()) {
100                            portletData._rawSB.writeTo(writer);
101    
102                            if (!portletData._auiModulesSet.isEmpty()) {
103                                    auiModulesSB.append(portletData._auiCallbackSB);
104                            }
105    
106                            if (!portletData._es6ModulesSet.isEmpty()) {
107                                    es6ModulesSB.append(portletData._es6CallbackSB);
108                            }
109    
110                            auiModulesSet.addAll(portletData._auiModulesSet);
111                            es6ModulesSet.addAll(portletData._es6ModulesSet);
112                    }
113    
114                    if ((auiModulesSB.index() == 0) && (es6ModulesSB.index() == 0)) {
115                            writer.write("\n// ]]>\n</script>");
116    
117                            return;
118                    }
119    
120                    if (!es6ModulesSet.isEmpty()) {
121                            writer.write("require(");
122    
123                            Iterator<String> iterator = es6ModulesSet.iterator();
124    
125                            while (iterator.hasNext()) {
126                                    writer.write(StringPool.APOSTROPHE);
127                                    writer.write(iterator.next());
128                                    writer.write(StringPool.APOSTROPHE);
129    
130                                    if (iterator.hasNext()) {
131                                            writer.write(StringPool.COMMA_AND_SPACE);
132                                    }
133                            }
134    
135                            writer.write(StringPool.COMMA_AND_SPACE);
136                            writer.write("function(");
137    
138                            iterator = es6ModulesSet.iterator();
139    
140                            Set<String> variableNames = new HashSet<>(es6ModulesSet.size());
141    
142                            while (iterator.hasNext()) {
143                                    writer.write(_generateVariable(iterator.next(), variableNames));
144    
145                                    if (iterator.hasNext()) {
146                                            writer.write(StringPool.COMMA_AND_SPACE);
147                                    }
148                            }
149    
150                            writer.write(") {\n");
151    
152                            es6ModulesSB.writeTo(writer);
153    
154                            writer.write("},\nfunction(error) {\nconsole.error(error);\n});");
155                    }
156    
157                    if (!auiModulesSet.isEmpty()) {
158                            writer.write("AUI().use(");
159    
160                            for (String use : auiModulesSet) {
161                                    writer.write(StringPool.APOSTROPHE);
162                                    writer.write(use);
163                                    writer.write(StringPool.APOSTROPHE);
164                                    writer.write(StringPool.COMMA_AND_SPACE);
165                            }
166    
167                            writer.write("function(A) {");
168    
169                            auiModulesSB.writeTo(writer);
170    
171                            writer.write("});");
172                    }
173    
174                    writer.write("\n// ]]>\n</script>");
175            }
176    
177            public static enum ModulesType {
178    
179                    AUI, ES6
180    
181            }
182    
183            private void _addToSBIndexList(StringBundler sb) {
184                    ObjectValuePair<StringBundler, Integer> ovp = new ObjectValuePair<>(
185                            sb, sb.index());
186    
187                    int index = _sbIndexList.indexOf(ovp);
188    
189                    if (index == -1) {
190                            _sbIndexList.add(ovp);
191                    }
192                    else {
193                            ovp = _sbIndexList.get(index);
194    
195                            ovp.setValue(sb.index());
196                    }
197            }
198    
199            private String _generateVariable(String name, Set<String> names) {
200                    StringBuilder sb = new StringBuilder(name.length());
201    
202                    char c = name.charAt(0);
203    
204                    boolean modified = true;
205    
206                    if ((CharPool.LOWER_CASE_A <= c) && (c <= CharPool.LOWER_CASE_Z) ||
207                            (c == CharPool.UNDERLINE)) {
208    
209                            sb.append(c);
210    
211                            modified = false;
212                    }
213                    else if ((CharPool.UPPER_CASE_A <= c) && (c <= CharPool.UPPER_CASE_Z)) {
214                            sb.append((char)(c + 32));
215                    }
216                    else {
217                            sb.append(CharPool.UNDERLINE);
218                    }
219    
220                    boolean startNewWord = false;
221    
222                    for (int i = 1; i < name.length(); i++) {
223                            c = name.charAt(i);
224    
225                            if ((CharPool.LOWER_CASE_A <= c) && (c <= CharPool.LOWER_CASE_Z)) {
226                                    if (startNewWord) {
227                                            sb.append((char)(c - 32));
228    
229                                            startNewWord = false;
230                                    }
231                                    else {
232                                            sb.append(c);
233                                    }
234                            }
235                            else if ((CharPool.UPPER_CASE_A <= c) &&
236                                             (c <= CharPool.UPPER_CASE_Z) ||
237                                             ((CharPool.NUMBER_0 <= c) && (c <= CharPool.NUMBER_9)) ||
238                                             (c == CharPool.UNDERLINE)) {
239    
240                                    sb.append(c);
241    
242                                    startNewWord = false;
243                            }
244                            else {
245                                    modified = true;
246    
247                                    startNewWord = true;
248                            }
249                    }
250    
251                    String safeName = name;
252    
253                    if (modified) {
254                            safeName = sb.toString();
255    
256                            name = safeName;
257                    }
258    
259                    int i = 1;
260    
261                    while (!names.add(name)) {
262                            name = safeName.concat(String.valueOf(i++));
263                    }
264    
265                    return name;
266            }
267    
268            private PortletData _getPortletData(String portletId) {
269                    if (Validator.isNull(portletId)) {
270                            portletId = StringPool.BLANK;
271                    }
272    
273                    PortletData portletData = _portletDataMap.get(portletId);
274    
275                    if (portletData == null) {
276                            portletData = new PortletData();
277    
278                            PortletData oldPortletData = _portletDataMap.putIfAbsent(
279                                    portletId, portletData);
280    
281                            if (oldPortletData != null) {
282                                    portletData = oldPortletData;
283                            }
284                    }
285    
286                    return portletData;
287            }
288    
289            private static final long serialVersionUID = 1L;
290    
291            private final ConcurrentMap<String, PortletData> _portletDataMap =
292                    new ConcurrentHashMap<>();
293            private final List<ObjectValuePair<StringBundler, Integer>> _sbIndexList =
294                    new ArrayList<>();
295    
296            private static class PortletData implements Serializable {
297    
298                    public void append(
299                            String content, String modules, ModulesType modulesType) {
300    
301                            if (Validator.isNull(modules)) {
302                                    _rawSB.append(content);
303                            }
304                            else {
305                                    String[] modulesArray = StringUtil.split(modules);
306    
307                                    if (modulesType == ModulesType.AUI) {
308                                            _auiCallbackSB.append("(function() {");
309                                            _auiCallbackSB.append(content);
310                                            _auiCallbackSB.append("})();");
311    
312                                            for (String module : modulesArray) {
313                                                    _auiModulesSet.add(StringUtil.trim(module));
314                                            }
315                                    }
316                                    else {
317                                            _es6CallbackSB.append("(function() {");
318                                            _es6CallbackSB.append(content);
319                                            _es6CallbackSB.append("})();");
320    
321                                            for (String module : modulesArray) {
322                                                    _es6ModulesSet.add(StringUtil.trim(module));
323                                            }
324                                    }
325                            }
326                    }
327    
328                    public void append(
329                            StringBundler contentSB, String modules, ModulesType modulesType) {
330    
331                            if (Validator.isNull(modules)) {
332                                    _rawSB.append(contentSB);
333                            }
334                            else {
335                                    String[] modulesArray = StringUtil.split(modules);
336    
337                                    if (modulesType == ModulesType.AUI) {
338                                            _auiCallbackSB.append("(function() {");
339                                            _auiCallbackSB.append(contentSB);
340                                            _auiCallbackSB.append("})();");
341    
342                                            for (String module : modulesArray) {
343                                                    _auiModulesSet.add(StringUtil.trim(module));
344                                            }
345                                    }
346                                    else {
347                                            _es6CallbackSB.append("(function() {");
348                                            _es6CallbackSB.append(contentSB);
349                                            _es6CallbackSB.append("})();");
350    
351                                            for (String module : modulesArray) {
352                                                    _es6ModulesSet.add(StringUtil.trim(module));
353                                            }
354                                    }
355                            }
356                    }
357    
358                    private static final long serialVersionUID = 1L;
359    
360                    private final StringBundler _auiCallbackSB = new StringBundler();
361                    private final Set<String> _auiModulesSet = new HashSet<>();
362                    private final StringBundler _es6CallbackSB = new StringBundler();
363                    private final Set<String> _es6ModulesSet = new HashSet<>();
364                    private final StringBundler _rawSB = new StringBundler();
365    
366            }
367    
368    }