001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.tools;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
018    import com.liferay.portal.kernel.io.unsync.UnsyncPrintWriter;
019    import com.liferay.portal.kernel.util.FastDateFormatFactoryUtil;
020    import com.liferay.portal.kernel.util.FileUtil;
021    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
022    import com.liferay.portal.kernel.util.PropsUtil;
023    import com.liferay.portal.kernel.util.StringPool;
024    import com.liferay.portal.kernel.util.StringUtil;
025    import com.liferay.portal.kernel.util.SystemProperties;
026    import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.model.ModelHintsConstants;
029    import com.liferay.portal.scripting.ruby.RubyExecutor;
030    import com.liferay.portal.servlet.filters.aggregate.AggregateFilter;
031    import com.liferay.portal.servlet.filters.aggregate.FileAggregateContext;
032    import com.liferay.portal.servlet.filters.dynamiccss.RTLCSSUtil;
033    import com.liferay.portal.util.FastDateFormatFactoryImpl;
034    import com.liferay.portal.util.FileImpl;
035    import com.liferay.portal.util.PortalImpl;
036    import com.liferay.portal.util.PortalUtil;
037    import com.liferay.portal.util.PropsImpl;
038    
039    import java.io.File;
040    
041    import java.util.ArrayList;
042    import java.util.HashMap;
043    import java.util.List;
044    import java.util.Map;
045    
046    import org.apache.tools.ant.DirectoryScanner;
047    
048    /**
049     * @author Brian Wing Shun Chan
050     * @author Raymond Aug??
051     * @author Eduardo Lundgren
052     */
053    public class SassToCssBuilder {
054    
055            public static File getCacheFile(String fileName) {
056                    return getCacheFile(fileName, StringPool.BLANK);
057            }
058    
059            public static File getCacheFile(String fileName, String suffix) {
060                    return new File(getCacheFileName(fileName, suffix));
061            }
062    
063            public static String getCacheFileName(String fileName, String suffix) {
064                    String cacheFileName = StringUtil.replace(
065                            fileName, StringPool.BACK_SLASH, StringPool.SLASH);
066    
067                    int x = cacheFileName.lastIndexOf(StringPool.SLASH);
068                    int y = cacheFileName.lastIndexOf(StringPool.PERIOD);
069    
070                    return cacheFileName.substring(0, x + 1) + ".sass-cache/" +
071                            cacheFileName.substring(x + 1, y) + suffix +
072                                    cacheFileName.substring(y);
073            }
074    
075            public static String getContent(String docrootDirName, String fileName)
076                    throws Exception {
077    
078                    File file = new File(docrootDirName.concat(fileName));
079    
080                    String content = FileUtil.read(file);
081    
082                    content = AggregateFilter.aggregateCss(
083                            new FileAggregateContext(docrootDirName, fileName), content);
084    
085                    return parseStaticTokens(content);
086            }
087    
088            public static String getRtlCustomFileName(String fileName) {
089                    int pos = fileName.lastIndexOf(StringPool.PERIOD);
090    
091                    return fileName.substring(0, pos) + "_rtl" + fileName.substring(pos);
092            }
093    
094            public static void main(String[] args) throws Exception {
095                    Map<String, String> arguments = ArgumentsUtil.parseArguments(args);
096    
097                    List<String> dirNames = new ArrayList<String>();
098    
099                    String dirName = arguments.get("sass.dir");
100    
101                    if (Validator.isNotNull(dirName)) {
102                            dirNames.add(dirName);
103                    }
104                    else {
105                            for (int i = 0;; i++ ) {
106                                    dirName = arguments.get("sass.dir." + i);
107    
108                                    if (Validator.isNotNull(dirName)) {
109                                            dirNames.add(dirName);
110                                    }
111                                    else {
112                                            break;
113                                    }
114                            }
115                    }
116    
117                    String docrootDirName = arguments.get("sass.docroot.dir");
118                    String portalCommonDirName = arguments.get("sass.portal.common.dir");
119    
120                    new SassToCssBuilder(dirNames, docrootDirName, portalCommonDirName);
121            }
122    
123            public static String parseStaticTokens(String content) {
124                    return StringUtil.replace(
125                            content,
126                            new String[] {
127                                    "@model_hints_constants_text_display_height@",
128                                    "@model_hints_constants_text_display_width@",
129                                    "@model_hints_constants_textarea_display_height@",
130                                    "@model_hints_constants_textarea_display_width@"
131                            },
132                            new String[] {
133                                    ModelHintsConstants.TEXT_DISPLAY_HEIGHT,
134                                    ModelHintsConstants.TEXT_DISPLAY_WIDTH,
135                                    ModelHintsConstants.TEXTAREA_DISPLAY_HEIGHT,
136                                    ModelHintsConstants.TEXTAREA_DISPLAY_WIDTH
137                            });
138            }
139    
140            public SassToCssBuilder(
141                            List<String> dirNames, String docrootDirName,
142                            String portalCommonDirName)
143                    throws Exception {
144    
145                    Class<?> clazz = getClass();
146    
147                    ClassLoader classLoader = clazz.getClassLoader();
148    
149                    _initUtil(classLoader);
150    
151                    _rubyScript = StringUtil.read(
152                            classLoader,
153                            "com/liferay/portal/servlet/filters/dynamiccss" +
154                                    "/dependencies/main.rb");
155    
156                    _tempDir = SystemProperties.get(SystemProperties.TMP_DIR);
157    
158                    for (String dirName : dirNames) {
159    
160                            // Create a new Ruby executor as a workaround for a bug with Ruby
161                            // that breaks "ant build-css" when it parses too many CSS files
162    
163                            _rubyExecutor = new RubyExecutor();
164    
165                            _rubyExecutor.setExecuteInSeparateThread(false);
166    
167                            _parseSassDirectory(dirName, docrootDirName, portalCommonDirName);
168                    }
169            }
170    
171            private void _cacheSass(
172                            String docrootDirName, String portalCommonDirName, String fileName)
173                    throws Exception {
174    
175                    if (fileName.contains("_rtl") || RTLCSSUtil.isExcludedPath(fileName)) {
176                            return;
177                    }
178    
179                    File cacheFile = getCacheFile(docrootDirName.concat(fileName));
180    
181                    String parsedContent = _parseSassFile(
182                            docrootDirName, portalCommonDirName, fileName);
183    
184                    FileUtil.write(cacheFile, parsedContent);
185    
186                    File file = new File(docrootDirName.concat(fileName));
187    
188                    long lastModified = file.lastModified();
189    
190                    cacheFile.setLastModified(lastModified);
191    
192                    // Generate RTL cache
193    
194                    File rtlCacheFile = getCacheFile(
195                            docrootDirName.concat(fileName), "_rtl");
196    
197                    String rtlCss = RTLCSSUtil.getRtlCss(fileName, parsedContent);
198    
199                    // Append custom CSS for RTL
200    
201                    String rtlCustomFileName = getRtlCustomFileName(fileName);
202    
203                    File rtlCustomFile = new File(docrootDirName, rtlCustomFileName);
204    
205                    if (rtlCustomFile.exists()) {
206                            lastModified = rtlCustomFile.lastModified();
207    
208                            String rtlCustomCss = _parseSassFile(
209                                    docrootDirName, portalCommonDirName, rtlCustomFileName);
210    
211                            rtlCss += rtlCustomCss;
212                    }
213    
214                    FileUtil.write(rtlCacheFile, rtlCss);
215    
216                    rtlCacheFile.setLastModified(lastModified);
217            }
218    
219            private String _getCssThemePath(String fileName) {
220                    int pos = fileName.lastIndexOf("/css/");
221    
222                    return fileName.substring(0, pos + 4);
223            }
224    
225            private void _initUtil(ClassLoader classLoader) {
226                    FastDateFormatFactoryUtil fastDateFormatFactoryUtil =
227                            new FastDateFormatFactoryUtil();
228    
229                    fastDateFormatFactoryUtil.setFastDateFormatFactory(
230                            new FastDateFormatFactoryImpl());
231    
232                    FileUtil fileUtil = new FileUtil();
233    
234                    fileUtil.setFile(new FileImpl());
235    
236                    PortalClassLoaderUtil.setClassLoader(classLoader);
237    
238                    PortalUtil portalUtil = new PortalUtil();
239    
240                    portalUtil.setPortal(new PortalImpl());
241    
242                    PropsUtil.setProps(new PropsImpl());
243    
244                    RTLCSSUtil.init();
245            }
246    
247            private boolean _isModified(String dirName, String[] fileNames)
248                    throws Exception {
249    
250                    for (String fileName : fileNames) {
251                            if (fileName.contains("_rtl")) {
252                                    continue;
253                            }
254    
255                            fileName = _normalizeFileName(dirName, fileName);
256    
257                            File file = new File(fileName);
258                            File cacheFile = getCacheFile(fileName);
259    
260                            if (file.lastModified() != cacheFile.lastModified()) {
261                                    return true;
262                            }
263                    }
264    
265                    return false;
266            }
267    
268            private String _normalizeFileName(String dirName, String fileName) {
269                    return StringUtil.replace(
270                            dirName + StringPool.SLASH + fileName,
271                            new String[] {
272                                    StringPool.BACK_SLASH, StringPool.DOUBLE_SLASH
273                            },
274                            new String[] {
275                                    StringPool.SLASH, StringPool.SLASH
276                            }
277                    );
278            }
279    
280            private void _parseSassDirectory(
281                            String dirName, String docrootDirName, String portalCommonDirName)
282                    throws Exception {
283    
284                    DirectoryScanner directoryScanner = new DirectoryScanner();
285    
286                    String basedir = docrootDirName.concat(dirName);
287    
288                    directoryScanner.setBasedir(basedir);
289    
290                    directoryScanner.setExcludes(
291                            new String[] {
292                                    "**\\_diffs\\**", "**\\.sass-cache*\\**",
293                                    "**\\.sass_cache_*\\**", "**\\_sass_cache_*\\**",
294                                    "**\\_styled\\**", "**\\_unstyled\\**"
295                            });
296                    directoryScanner.setIncludes(new String[] {"**\\*.css"});
297    
298                    directoryScanner.scan();
299    
300                    String[] fileNames = directoryScanner.getIncludedFiles();
301    
302                    if (!_isModified(basedir, fileNames)) {
303                            return;
304                    }
305    
306                    for (String fileName : fileNames) {
307                            fileName = _normalizeFileName(dirName, fileName);
308    
309                            try {
310                                    long start = System.currentTimeMillis();
311    
312                                    _cacheSass(docrootDirName, portalCommonDirName, fileName);
313    
314                                    long end = System.currentTimeMillis();
315    
316                                    System.out.println(
317                                            "Parsed " + docrootDirName + fileName + " in " +
318                                                    (end - start) + " ms");
319                            }
320                            catch (Exception e) {
321                                    System.out.println("Unable to parse " + fileName);
322    
323                                    throw e;
324                            }
325                    }
326            }
327    
328            private String _parseSassFile(
329                            String docrootDirName, String portalCommonDirName, String fileName)
330                    throws Exception {
331    
332                    String filePath = docrootDirName.concat(fileName);
333    
334                    Map<String, Object> inputObjects = new HashMap<String, Object>();
335    
336                    inputObjects.put("commonSassPath", portalCommonDirName);
337                    inputObjects.put("content", getContent(docrootDirName, fileName));
338                    inputObjects.put("cssRealPath", filePath);
339                    inputObjects.put("cssThemePath", _getCssThemePath(filePath));
340                    inputObjects.put("sassCachePath", _tempDir);
341    
342                    UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
343                            new UnsyncByteArrayOutputStream();
344    
345                    UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(
346                            unsyncByteArrayOutputStream);
347    
348                    inputObjects.put("out", unsyncPrintWriter);
349    
350                    _rubyExecutor.eval(null, inputObjects, null, _rubyScript);
351    
352                    unsyncPrintWriter.flush();
353    
354                    return unsyncByteArrayOutputStream.toString();
355            }
356    
357            private RubyExecutor _rubyExecutor;
358            private String _rubyScript;
359            private String _tempDir;
360    
361    }