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.tools.sass;
016    
017    import com.liferay.portal.kernel.util.CharPool;
018    import com.liferay.portal.kernel.util.FileUtil;
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    import com.liferay.portal.servlet.filters.dynamiccss.RTLCSSUtil;
024    import com.liferay.portal.tools.SassToCssBuilder;
025    import com.liferay.portal.util.AggregateUtil;
026    
027    import java.io.File;
028    
029    import java.util.ArrayList;
030    import java.util.List;
031    import java.util.concurrent.Callable;
032    
033    /**
034     * @author Minhchau Dang
035     * @author Shuyang Zhou
036     */
037    public class SassFile implements Callable<Void>, SassFragment {
038    
039            public SassFile(String docrootDirName, String fileName) {
040                    _docrootDirName = docrootDirName;
041                    _fileName = fileName;
042    
043                    int pos = fileName.lastIndexOf(CharPool.SLASH);
044    
045                    if (pos != -1) {
046                            _baseDir = fileName.substring(0, pos + 1);
047                    }
048                    else {
049                            _baseDir = StringPool.BLANK;
050                    }
051            }
052    
053            @Override
054            public Void call() throws Exception {
055                    long start = System.currentTimeMillis();
056    
057                    File file = new File(_docrootDirName, _fileName);
058    
059                    if (!file.exists()) {
060                            return null;
061                    }
062    
063                    String content = FileUtil.read(file);
064    
065                    int pos = 0;
066    
067                    StringBundler sb = new StringBundler();
068    
069                    while (true) {
070                            int commentX = content.indexOf(_CSS_COMMENT_BEGIN, pos);
071                            int commentY = content.indexOf(
072                                    _CSS_COMMENT_END, commentX + _CSS_COMMENT_BEGIN.length());
073    
074                            int importX = content.indexOf(_CSS_IMPORT_BEGIN, pos);
075                            int importY = content.indexOf(
076                                    _CSS_IMPORT_END, importX + _CSS_IMPORT_BEGIN.length());
077    
078                            if ((importX == -1) || (importY == -1)) {
079                                    sb.append(content.substring(pos));
080    
081                                    break;
082                            }
083                            else if ((commentX != -1) && (commentY != -1) &&
084                                             (commentX < importX) && (commentY > importX)) {
085    
086                                    commentY += _CSS_COMMENT_END.length();
087    
088                                    sb.append(content.substring(pos, commentY));
089    
090                                    pos = commentY;
091                            }
092                            else {
093                                    sb.append(content.substring(pos, importX));
094    
095                                    String mediaQuery = StringPool.BLANK;
096    
097                                    int mediaQueryImportX = content.indexOf(
098                                            CharPool.CLOSE_PARENTHESIS,
099                                            importX + _CSS_IMPORT_BEGIN.length());
100                                    int mediaQueryImportY = content.indexOf(
101                                            CharPool.SEMICOLON, importX + _CSS_IMPORT_BEGIN.length());
102    
103                                    String importFileName = null;
104    
105                                    if (importY != mediaQueryImportX) {
106                                            mediaQuery = content.substring(
107                                                    mediaQueryImportX + 1, mediaQueryImportY);
108    
109                                            importFileName = content.substring(
110                                                    importX + _CSS_IMPORT_BEGIN.length(),
111                                                    mediaQueryImportX);
112                                    }
113                                    else {
114                                            importFileName = content.substring(
115                                                    importX + _CSS_IMPORT_BEGIN.length(), importY);
116                                    }
117    
118                                    if (!importFileName.isEmpty()) {
119                                            if (importFileName.charAt(0) != CharPool.SLASH) {
120                                                    importFileName = _fixRelativePath(
121                                                            _baseDir.concat(importFileName));
122                                            }
123    
124                                            SassFile importSassFile = SassExecutorUtil.execute(
125                                                    _docrootDirName, importFileName);
126    
127                                            if (Validator.isNotNull(mediaQuery)) {
128                                                    _sassFragments.add(
129                                                            new SassFileWithMediaQuery(
130                                                                    importSassFile, mediaQuery));
131                                            }
132                                            else {
133                                                    _sassFragments.add(importSassFile);
134                                            }
135                                    }
136    
137                                    // LEP-7540
138    
139                                    if (Validator.isNotNull(mediaQuery)) {
140                                            pos = mediaQueryImportY + 1;
141                                    }
142                                    else {
143                                            pos = importY + _CSS_IMPORT_END.length();
144                                    }
145                            }
146                    }
147    
148                    _addSassString(_fileName, sb.toString());
149    
150                    String rtlCustomFileName = SassToCssBuilder.getRtlCustomFileName(
151                            _fileName);
152    
153                    File rtlCustomFile = new File(_docrootDirName, rtlCustomFileName);
154    
155                    if (rtlCustomFile.exists()) {
156                            _addSassString(rtlCustomFileName, FileUtil.read(rtlCustomFile));
157                    }
158    
159                    _elapsedTime = System.currentTimeMillis() - start;
160    
161                    return null;
162            }
163    
164            @Override
165            public String getLtrContent() {
166                    if (_ltrContent != null) {
167                            return _ltrContent;
168                    }
169    
170                    StringBundler sb = new StringBundler(_sassFragments.size());
171    
172                    for (SassFragment sassFragment : _sassFragments) {
173                            String ltrContent = sassFragment.getLtrContent();
174    
175                            if (sassFragment instanceof SassFile) {
176                                    SassFile sassFile = (SassFile)sassFragment;
177    
178                                    String baseURL = _BASE_URL.concat(sassFile._baseDir);
179    
180                                    ltrContent = AggregateUtil.updateRelativeURLs(
181                                            ltrContent, baseURL);
182                            }
183    
184                            sb.append(ltrContent);
185                    }
186    
187                    _ltrContent = sb.toString();
188    
189                    return _ltrContent;
190            }
191    
192            @Override
193            public String getRtlContent() {
194                    if (_rtlContent != null) {
195                            return _rtlContent;
196                    }
197    
198                    StringBundler sb = new StringBundler(_sassFragments.size());
199    
200                    for (SassFragment sassFragment : _sassFragments) {
201                            String rtlContent = sassFragment.getRtlContent();
202    
203                            if (sassFragment instanceof SassFile) {
204                                    SassFile sassFile = (SassFile)sassFragment;
205    
206                                    String baseURL = _BASE_URL.concat(sassFile._baseDir);
207    
208                                    rtlContent = AggregateUtil.updateRelativeURLs(
209                                            rtlContent, baseURL);
210                            }
211    
212                            sb.append(rtlContent);
213                    }
214    
215                    _rtlContent = sb.toString();
216    
217                    return _rtlContent;
218            }
219    
220            @Override
221            public String toString() {
222                    StringBundler sb = new StringBundler(5);
223    
224                    sb.append("Parsed ");
225                    sb.append(_fileName);
226                    sb.append(" in ");
227                    sb.append(_elapsedTime);
228                    sb.append("ms");
229    
230                    return sb.toString();
231            }
232    
233            public void writeCacheFiles() throws Exception {
234                    File ltrCacheFile = new File(
235                            _docrootDirName,
236                            SassToCssBuilder.getCacheFileName(_fileName, StringPool.BLANK));
237    
238                    FileUtil.write(ltrCacheFile, getLtrContent());
239    
240                    File ltrFile = new File(_docrootDirName, _fileName);
241    
242                    ltrCacheFile.setLastModified(ltrFile.lastModified());
243    
244                    String rtlFileName = SassToCssBuilder.getRtlCustomFileName(_fileName);
245    
246                    if (RTLCSSUtil.isExcludedPath(_fileName)) {
247                            return;
248                    }
249    
250                    File rtlCacheFile = new File(
251                            _docrootDirName,
252                            SassToCssBuilder.getCacheFileName(rtlFileName, StringPool.BLANK));
253    
254                    FileUtil.write(rtlCacheFile, getRtlContent());
255    
256                    rtlCacheFile.setLastModified(ltrFile.lastModified());
257            }
258    
259            private void _addSassString(String fileName, String sassContent)
260                    throws Exception {
261    
262                    sassContent = sassContent.trim();
263    
264                    if (sassContent.isEmpty()) {
265                            return;
266                    }
267    
268                    _sassFragments.add(new SassString(fileName, sassContent));
269            }
270    
271            private String _fixRelativePath(String fileName) {
272                    String[] paths = StringUtil.split(fileName, CharPool.SLASH);
273    
274                    StringBundler sb = new StringBundler(paths.length * 2);
275    
276                    for (String path : paths) {
277                            if (path.isEmpty() || path.equals(StringPool.PERIOD)) {
278                                    continue;
279                            }
280    
281                            if (path.equals(StringPool.DOUBLE_PERIOD) && (sb.length() >= 2)) {
282                                    sb.setIndex(sb.index() - 2);
283    
284                                    continue;
285                            }
286    
287                            sb.append(StringPool.SLASH);
288                            sb.append(path);
289                    }
290    
291                    return sb.toString();
292            }
293    
294            private static final String _BASE_URL = "@base_url@";
295    
296            private static final String _CSS_COMMENT_BEGIN = "/*";
297    
298            private static final String _CSS_COMMENT_END = "*/";
299    
300            private static final String _CSS_IMPORT_BEGIN = "@import url(";
301    
302            private static final String _CSS_IMPORT_END = ");";
303    
304            private String _baseDir;
305            private String _docrootDirName;
306            private long _elapsedTime;
307            private String _fileName;
308            private String _ltrContent;
309            private String _rtlContent;
310            private List<SassFragment> _sassFragments = new ArrayList<SassFragment>();
311    
312    }