001    /**
002     * Copyright (c) 2000-2013 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;
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.util.FastDateFormatFactoryImpl;
033    import com.liferay.portal.util.FileImpl;
034    import com.liferay.portal.util.PortalImpl;
035    import com.liferay.portal.util.PortalUtil;
036    import com.liferay.portal.util.PropsImpl;
037    
038    import java.io.File;
039    
040    import java.util.ArrayList;
041    import java.util.HashMap;
042    import java.util.List;
043    import java.util.Map;
044    
045    import org.apache.tools.ant.DirectoryScanner;
046    
047    /**
048     * @author Brian Wing Shun Chan
049     * @author Raymond Aug??
050     * @author Eduardo Lundgren
051     */
052    public class SassToCssBuilder {
053    
054            public static File getCacheFile(String fileName) {
055                    fileName = StringUtil.replace(
056                            fileName, StringPool.BACK_SLASH, StringPool.SLASH);
057    
058                    int pos = fileName.lastIndexOf(StringPool.SLASH);
059    
060                    String cacheFileName =
061                            fileName.substring(0, pos + 1) + ".sass-cache/" +
062                                    fileName.substring(pos + 1);
063    
064                    return new File(cacheFileName);
065            }
066    
067            public static void main(String[] args) {
068                    Map<String, String> arguments = ArgumentsUtil.parseArguments(args);
069    
070                    List<String> dirNames = new ArrayList<String>();
071    
072                    String dirName = arguments.get("sass.dir");
073    
074                    if (Validator.isNotNull(dirName)) {
075                            dirNames.add(dirName);
076                    }
077                    else {
078                            for (int i = 0;; i++ ) {
079                                    dirName = arguments.get("sass.dir." + i);
080    
081                                    if (Validator.isNotNull(dirName)) {
082                                            dirNames.add(dirName);
083                                    }
084                                    else {
085                                            break;
086                                    }
087                            }
088                    }
089    
090                    String docrootDirName = arguments.get("sass.docroot.dir");
091                    String portalCommonDirName = arguments.get("sass.portal.common.dir");
092    
093                    try {
094                            new SassToCssBuilder(dirNames, docrootDirName, portalCommonDirName);
095                    }
096                    catch (Exception e) {
097                            e.printStackTrace();
098                    }
099            }
100    
101            public static String parseStaticTokens(String content) {
102                    return StringUtil.replace(
103                            content,
104                            new String[] {
105                                    "@model_hints_constants_text_display_height@",
106                                    "@model_hints_constants_text_display_width@",
107                                    "@model_hints_constants_textarea_display_height@",
108                                    "@model_hints_constants_textarea_display_width@"
109                            },
110                            new String[] {
111                                    ModelHintsConstants.TEXT_DISPLAY_HEIGHT,
112                                    ModelHintsConstants.TEXT_DISPLAY_WIDTH,
113                                    ModelHintsConstants.TEXTAREA_DISPLAY_HEIGHT,
114                                    ModelHintsConstants.TEXTAREA_DISPLAY_WIDTH
115                            });
116            }
117    
118            public SassToCssBuilder(
119                            List<String> dirNames, String docrootDirName,
120                            String portalCommonDirName)
121                    throws Exception {
122    
123                    Class<?> clazz = getClass();
124    
125                    ClassLoader classLoader = clazz.getClassLoader();
126    
127                    _initUtil(classLoader);
128    
129                    _rubyScript = StringUtil.read(
130                            classLoader,
131                            "com/liferay/portal/servlet/filters/dynamiccss/main.rb");
132    
133                    _tempDir = SystemProperties.get(SystemProperties.TMP_DIR);
134    
135                    for (String dirName : dirNames) {
136    
137                            // Create a new Ruby executor as a workaround for a bug with Ruby
138                            // that breaks "ant build-css" when it parses too many CSS files
139    
140                            _rubyExecutor = new RubyExecutor();
141    
142                            _rubyExecutor.setExecuteInSeparateThread(false);
143    
144                            _parseSassDirectory(dirName, docrootDirName, portalCommonDirName);
145                    }
146            }
147    
148            private String _getContent(String docrootDirName, String fileName)
149                    throws Exception {
150    
151                    File file = new File(docrootDirName.concat(fileName));
152    
153                    String content = FileUtil.read(file);
154    
155                    content = AggregateFilter.aggregateCss(
156                            new FileAggregateContext(docrootDirName, fileName), content);
157    
158                    return parseStaticTokens(content);
159            }
160    
161            private String _getCssThemePath(String fileName) {
162                    int pos = fileName.lastIndexOf("/css/");
163    
164                    return fileName.substring(0, pos + 4);
165            }
166    
167            private void _initUtil(ClassLoader classLoader) {
168                    FastDateFormatFactoryUtil fastDateFormatFactoryUtil =
169                            new FastDateFormatFactoryUtil();
170    
171                    fastDateFormatFactoryUtil.setFastDateFormatFactory(
172                            new FastDateFormatFactoryImpl());
173    
174                    FileUtil fileUtil = new FileUtil();
175    
176                    fileUtil.setFile(new FileImpl());
177    
178                    PortalClassLoaderUtil.setClassLoader(classLoader);
179    
180                    PortalUtil portalUtil = new PortalUtil();
181    
182                    portalUtil.setPortal(new PortalImpl());
183    
184                    PropsUtil.setProps(new PropsImpl());
185            }
186    
187            private boolean _isModified(String dirName, String[] fileNames)
188                    throws Exception {
189    
190                    for (String fileName : fileNames) {
191                            fileName = _normalizeFileName(dirName, fileName);
192    
193                            File file = new File(fileName);
194                            File cacheFile = getCacheFile(fileName);
195    
196                            if (file.lastModified() != cacheFile.lastModified()) {
197                                    return true;
198                            }
199                    }
200    
201                    return false;
202            }
203    
204            private String _normalizeFileName(String dirName, String fileName) {
205                    return StringUtil.replace(
206                            dirName + StringPool.SLASH + fileName,
207                            new String[] {
208                                    StringPool.BACK_SLASH, StringPool.DOUBLE_SLASH
209                            },
210                            new String[] {
211                                    StringPool.SLASH, StringPool.SLASH
212                            }
213                    );
214            }
215    
216            private void _parseSassDirectory(
217                            String dirName, String docrootDirName, String portalCommonDirName)
218                    throws Exception {
219    
220                    DirectoryScanner directoryScanner = new DirectoryScanner();
221    
222                    String basedir = docrootDirName.concat(dirName);
223    
224                    directoryScanner.setBasedir(basedir);
225    
226                    directoryScanner.setExcludes(
227                            new String[] {
228                                    "**\\_diffs\\**", "**\\.sass-cache*\\**",
229                                    "**\\.sass_cache_*\\**", "**\\_sass_cache_*\\**",
230                                    "**\\_styled\\**", "**\\_unstyled\\**"
231                            });
232                    directoryScanner.setIncludes(new String[] {"**\\*.css"});
233    
234                    directoryScanner.scan();
235    
236                    String[] fileNames = directoryScanner.getIncludedFiles();
237    
238                    if (!_isModified(basedir, fileNames)) {
239                            return;
240                    }
241    
242                    for (String fileName : fileNames) {
243                            fileName = _normalizeFileName(dirName, fileName);
244    
245                            try {
246                                    long start = System.currentTimeMillis();
247    
248                                    _parseSassFile(docrootDirName, portalCommonDirName, fileName);
249    
250                                    long end = System.currentTimeMillis();
251    
252                                    System.out.println(
253                                            "Parsed " + docrootDirName + fileName + " in " +
254                                                    (end - start) + " ms");
255                            }
256                            catch (Exception e) {
257                                    System.out.println("Unable to parse " + fileName);
258    
259                                    e.printStackTrace();
260                            }
261                    }
262            }
263    
264            private void _parseSassFile(
265                            String docrootDirName, String portalCommonDirName, String fileName)
266                    throws Exception {
267    
268                    String filePath = docrootDirName.concat(fileName);
269    
270                    File file = new File(filePath);
271                    File cacheFile = getCacheFile(filePath);
272    
273                    Map<String, Object> inputObjects = new HashMap<String, Object>();
274    
275                    inputObjects.put("commonSassPath", portalCommonDirName);
276                    inputObjects.put("content", _getContent(docrootDirName, fileName));
277                    inputObjects.put("cssRealPath", filePath);
278                    inputObjects.put("cssThemePath", _getCssThemePath(filePath));
279                    inputObjects.put("sassCachePath", _tempDir);
280    
281                    UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
282                            new UnsyncByteArrayOutputStream();
283    
284                    UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(
285                            unsyncByteArrayOutputStream);
286    
287                    inputObjects.put("out", unsyncPrintWriter);
288    
289                    _rubyExecutor.eval(null, inputObjects, null, _rubyScript);
290    
291                    unsyncPrintWriter.flush();
292    
293                    String parsedContent = unsyncByteArrayOutputStream.toString();
294    
295                    FileUtil.write(cacheFile, parsedContent);
296    
297                    cacheFile.setLastModified(file.lastModified());
298            }
299    
300            private RubyExecutor _rubyExecutor;
301            private String _rubyScript;
302            private String _tempDir;
303    
304    }