All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.designer.css.CN1CSSCLI Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.designer.css;



import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import com.codename1.designer.css.CSSTheme.WebViewProvider;
import com.codename1.impl.javase.CN1Bootstrap;
import com.codename1.io.Log;
import com.codename1.io.Util;
import com.codename1.ui.BrowserComponent;
import com.codename1.ui.CN;
import com.codename1.ui.Component;
import com.codename1.ui.ComponentSelector;
import com.codename1.ui.ComponentSelector.ComponentClosure;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.util.Resources;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.CopyOption;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.swing.JFrame;
import javax.swing.JPanel;
//import org.apache.tools.ant.types.Path;

/**
 *
 * @author shannah
 */
public class CN1CSSCLI {
    public static String version = "7.0";
    static Object lock = new Object();
    static BrowserComponent web;
    
    public void start() throws Exception {
        //Platform.setImplicitExit(false);
        startImpl();
        //stage.hide();
        
    }
    
    private static void startImpl() throws Exception {
        web = new BrowserComponent();
        web.addWebEventListener(BrowserComponent.onError, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                 System.out.println("Received exception: "+evt.getSource());
            }
            
        });
        web.setPreferredW(400);
        web.setPreferredH(800);
        
        //Scene scene = new Scene(web, 400, 800, Color.web("#666670"));
        //stage.setScene(scene);
        //stage.show();
        synchronized(lock) {
            lock.notify();
        }
    }
    
    private static void relaunch() throws Exception {
        //Stage stage = new Stage();
        startImpl();
    }
    
    
    // It's getting to be a bit of a gong show in here with the CSS compiler trying to 
    // work nicely with the project files - e.g. automatically saving the theme.res in the
    // src directory.
    // Stateless mode is an attempt to make it simpler.  
    // Stateless mode can be invoked by adding "-stateless" to the parameters.
    public static boolean statelessMode;
    public static boolean mergeMode;
    public static boolean watchmode;
    private static Thread watchThread;
    
    
    
    private static String getArgByName(String[] args, String... names) {
        int len = args.length;
        List namesList = Arrays.asList(names);
        for (int i=0; i 0 && arg.charAt(0) == '-' && namesList.contains(arg.substring(1))) {
                if (i + 1 < len) {
                    String nextArg = args[i+1];
                    if (nextArg.length() > 0 && nextArg.charAt(0) == '-') {
                        // If the next arg is another flag, then just return value "true"
                        // to confirm that the flag exists.
                        return "true";
                    } else {
                        return nextArg;
                    }
                } else {
                    // If this is the last arg, then just return "true" to verify that the
                    // flag exists.
                    return "true";
                }
            }
        }
        return null;
    }

    
    private static String getInputFile(String[] args) {
        if (statelessMode) {
            return getArgByName(args, "i", "input");
            
        }
        if (args.length > 0) {
            return args[0];
        } else {
            return "test.css";
        }
    }
    
    private static Properties loadProjectProperties(File projectDir) throws IOException {
        
        Properties out = new Properties();
        try (FileInputStream fis = new FileInputStream(new File(projectDir, "codenameone_settings.properties"))) {
            out.load(fis);
        };
        return out;
    }
    
    private static boolean isStatelessMode(String[] args) {
        for (String arg : args) {
            if ("-stateless".equals(arg)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean isMergeMode(String[] args) {
        String inputFile = getInputFile(args);
        File f = new File(inputFile);
        if (!"theme.css".equals(f.getName())) {
            return false;
        }
        try {
            File projectDir = getProjectDir(f);
            Properties props = loadProjectProperties(projectDir);
            return "true".equals(props.getProperty("codename1.cssTheme"));
            
        } catch (IOException ex) {
            return false;
        }
        
    }
    
    private static String prefixUrls(String contents, String prefix) {
        
        contents = contents.replaceAll("url\\(\"(.*?)\"\\)", "url($1)");
        contents = contents.replaceAll("url\\('(.*?)'\\)", "url($1)");
        contents = contents.replaceAll("url\\((.*?://.*?)\\)", "url(\"$1\")");
        contents = contents.replaceAll("url\\((/.*?)\\)", "url(\"$1\")");
        //contents = contents.replaceAll("url\\(((?!(.*://)).*?)?\\)", "url("+prefix+"$1)");
        contents = contents.replaceAll("url\\(([^\\\"\'].*?)\\)", "url(\""+prefix+"$1\")");
        return contents;
    }
    
    private static String getRelativePath(File file, File relativeTo) throws IOException {
        File projectDir = getProjectDir(file);
        if (!relativeTo.getAbsolutePath().startsWith(projectDir.getAbsolutePath())) {
            throw new IllegalArgumentException("Relative file not in project: "+relativeTo);
        }
        if (!file.getAbsolutePath().startsWith(projectDir.getAbsolutePath())) {
            throw new IllegalArgumentException("File not in project: "+file);
        }
        
        List fileAncestors = new ArrayList();
        File tmp = file;
        while (tmp.getParentFile() != null) {
            tmp = tmp.getParentFile();
            fileAncestors.add(tmp.getAbsolutePath());
            if (tmp.getAbsolutePath().equals(projectDir.getAbsolutePath())) {
                break;
            }
            
        }
        List relativeToAncestors = new ArrayList();
        tmp = relativeTo;
        while (tmp.getParentFile() != null) {
            tmp = tmp.getParentFile();
            relativeToAncestors.add(tmp.getAbsolutePath());
            if (tmp.getAbsolutePath().equals(projectDir.getAbsolutePath())) {
                break;
            }
            
        }
        String commonAncestor = null;
        for (String filePath : fileAncestors) {
            int index = relativeToAncestors.indexOf(filePath);
            if (index > -1) {
                commonAncestor = filePath;
                break;
            }
        }
        if (commonAncestor == null) {
            throw new IOException("No common ancestors found between "+file+" and "+relativeTo+".  FileAncestors: "+fileAncestors+", relativeToAncestors:"+relativeToAncestors);
        }
        int distanceUpToCommonAncestor = relativeTo.getAbsolutePath().substring(commonAncestor.length()+1).split(Pattern.quote(File.separator)).length-1;
        StringBuilder sb = new StringBuilder();
        for (int i=0; i destChildren = new HashSet();
        String[] children = destDir.list();
        if (children != null) {
            for (String child : children) {
                destChildren.add(child);
            }
        }
        for (File child : srcDir.listFiles()) {
            
            String childName = child.getName();
            File destChild = new File(destDir, childName);
            if (destChildren.contains(childName)) {
                
                if (child.isDirectory()) {
                    if (destChild.isDirectory()) {
                        syncDirectories(child, destChild);
                    } else {
                        destChild.delete();
                        destChild.mkdir();
                        syncDirectories(child, destChild);
                    }
                } else {
                    if (destChild.isDirectory()) {
                        delTree(destChild);
                        Files.copy(child.toPath(), destChild.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    }  else {
                        long destMtime = destChild.lastModified();
                        long srcMTime = child.lastModified();
                        if (destMtime < srcMTime) {
                            Files.copy(child.toPath(), destChild.toPath(), StandardCopyOption.REPLACE_EXISTING);
                        }
                    }
                }
            } else {
                
                if (child.isDirectory()) {
                    destChild.mkdir();
                    syncDirectories(child, destChild);
                } else {
                    Files.copy(child.toPath(), destChild.toPath(), StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
        
        
    }
    
    private static String getMd5(String input) 
    { 
        try { 
  
            // Static getInstance method is called with hashing MD5 
            MessageDigest md = MessageDigest.getInstance("MD5"); 
  
            // digest() method is called to calculate message digest 
            //  of an input digest() return array of byte 
            byte[] messageDigest = md.digest(input.getBytes()); 
  
            // Convert byte array into signum representation 
            BigInteger no = new BigInteger(1, messageDigest); 
  
            // Convert message digest into hex value 
            String hashtext = no.toString(16); 
            while (hashtext.length() < 32) { 
                hashtext = "0" + hashtext; 
            } 
            return hashtext; 
        }  
  
        // For specifying wrong message digest algorithms 
        catch (NoSuchAlgorithmException e) { 
            throw new RuntimeException(e); 
        } 
    } 
    
    /**
     * Checks if directory 1 contains directory 2
     * @param directory1
     * @param directory2
     * @return true if directory 1 contains directory 2
     */
    private static boolean contains(File directory1, File directory2) throws IOException {
        File canonical1 = directory1.getCanonicalFile();
        File canonical2 = directory2.getCanonicalFile();
        File parent2 = canonical2.getParentFile();
        if (parent2 == null) {
            return false;
        }
        if (canonical1.equals(parent2)) {
            return true;
        }
        return contains(directory2, parent2);
    }
    
    /**
     * Updates the given merged CSS file with the contents of the given input files.  This is only used when
     * running in stateless mode.
     * @param inputFiles List of input CSS files
     * @param mergedFile Output CSS file
     * @throws IOException If there is a problem writing the merged file.
     */
    private static void updateMergeFileStateless(File[] inputFiles, File mergedFile) throws IOException {
        boolean changed = false;
        
        long mergedFileLastModified = mergedFile.exists() ? mergedFile.lastModified() : 0l;
        for (File inputFile : inputFiles) {
            if (mergedFileLastModified < inputFile.lastModified()) {
                changed = true;
                break;
            }
        }
        
        if (!changed) {
            return;
        }
        
        File mergeRoot = mergedFile.getParentFile();
        File incDir = new File(mergeRoot, "cn1-merged-files");
        incDir.mkdir();
        
        
        StringBuilder buf = new StringBuilder();
        
        //File libCSSDir = getLibCSSDirectory(inputFile);
        //String relativePathToLibCSSDir = getRelativePath(libCSSDir, mergedFile);
        
        for (File f : inputFiles) {

            File canonicalFile = f.getCanonicalFile();
            String md5 = getMd5(canonicalFile.getAbsolutePath());
            File destDir = new File(incDir, md5);
            syncDirectories(canonicalFile.getParentFile(), destDir);
            
            String contents;
            try (FileInputStream fis = new FileInputStream(f)) {
                byte[] buffer = new byte[(int)f.length()];
                fis.read(buffer);
                contents = new String(buffer, "UTF-8");
            }
            
            contents = prefixUrls(contents, "cn1-merged-files/"+md5+"/");
            buf.append("\n/* "+f.getAbsolutePath()+" */\n").append(contents).append("\n/* end "+f.getAbsolutePath()+"*/\n");
        }
       
        try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
            fos.write(buf.toString().getBytes("UTF-8"));
        }
        
        
    }
    
    private static void updateMergeFile(File[] inputFiles, File mergedFile) throws IOException {
        if (statelessMode) {
            updateMergeFileStateless(inputFiles, mergedFile);
            return;
        } 
        File inputFile = inputFiles[0];
        List libCSSFiles = findLibCSSFiles(inputFile);
        boolean changed = false;
        if (!mergedFile.exists() || mergedFile.lastModified() < inputFile.lastModified()) {
            changed = true;
        }
        if (!changed) {
            for (File f : libCSSFiles) {
                if (mergedFile.lastModified() < f.lastModified()) {
                    changed = true;
                    break;
                }
            }
        }
        if (!changed) {
            return;
        }
        StringBuilder buf = new StringBuilder();
        
        File libCSSDir = getLibCSSDirectory(inputFile);
        String relativePathToLibCSSDir = getRelativePath(libCSSDir, mergedFile);
        
        for (File f : libCSSFiles) {
            String contents;
            try (FileInputStream fis = new FileInputStream(f)) {
                byte[] buffer = new byte[(int)f.length()];
                fis.read(buffer);
                contents = new String(buffer, "UTF-8");
            }
            contents = prefixUrls(contents, relativePathToLibCSSDir+"/"+f.getParentFile().getName()+"/");
            buf.append("\n/* "+f.getAbsolutePath()+" */\n").append(contents).append("\n/* end "+f.getAbsolutePath()+"*/\n");
        }
        
        try (FileInputStream fis = new FileInputStream(inputFile)) {
            buf.append("\n/* ").append(inputFile.getAbsolutePath()).append(" */\n");
            byte[] buffer = new byte[(int)inputFile.length()];
            fis.read(buffer);
            String fileContents = new String(buffer, "UTF-8");
            if (!mergedFile.getParentFile().getAbsolutePath().equals(inputFile.getParentFile().getAbsolutePath())) {
                // Merged file is in a different directory than the theme.css file.
                // We need to update the relative URL paths.
                String relativePathToInputFileDir = getRelativePath(inputFile.getParentFile(), mergedFile);
                fileContents = prefixUrls(fileContents, relativePathToInputFileDir+"/");

            }
            buf.append(fileContents);
            buf.append("\n/* End ").append(inputFile.getAbsolutePath()).append(" */\n");
        }
        
        try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
            fos.write(buf.toString().getBytes("UTF-8"));
        }
        
        
        
        
    }
    
   
    
    private static  File getProjectDir(File start) {
        File f = new File(start, "codenameone_settings.properties");
        
        while (!f.exists() && f.getParentFile().getParentFile() != null) {
            f = new File(f.getParentFile().getParentFile(), "codenameone_settings.properties");
            if (f.exists()) {
                return f.getParentFile();
            }
        }
        return f.exists() ? f.getParentFile() : null;
        
    }
    
    private static File getLibCSSDirectory(File inputFile) throws IOException {
        if (System.getProperty("cn1.libCSSDir", null) != null) {
            return new File(System.getProperty("cn1.libCSSDir"));
        }
        if (isMavenProject(inputFile)) {
            return new File(getProjectDir(inputFile), "target" + File.separator + "css");
        }
        return new File(getProjectDir(inputFile), 
                "lib" + File.separator + "impl" +File.separator + "css"
        );
    }
    
    private static List findLibCSSFiles(File inputFile) throws IOException {
        ArrayList out = new ArrayList<>();
        File cssDir = getLibCSSDirectory(inputFile);
        if (cssDir.exists() && cssDir.isDirectory()) {
            for (File child : cssDir.listFiles()) {
                if (child.isDirectory()) {
                    File themeCss = new File(child, "theme.css");
                    if (themeCss.exists()) {
                        out.add(themeCss);
                    }
                }
            }
        }
        
        return out;
       
    }
    
    private static boolean isMavenProject(File inputFile) throws IOException {
        return new File(getProjectDir(inputFile), "pom.xml").exists();
    }
    
    private static String getMergedFile(String inputPath) throws IOException {
        if (System.getProperty("cn1.cssMergeFile") != null) {
            System.getProperty("cn1.cssMergeFile");
        }
        File inputFile = new File(inputPath);
        if (isMavenProject(inputFile)) {
            return new File(getLibCSSDirectory(inputFile), inputFile.getName()+".merged").getAbsolutePath();
        }
        return inputPath + ".merged";
    }
    
    private static boolean checkWatchMode(String[] args) {
        for (int i=0; i out = new ArrayList();
        if (inputPath.contains(",")) {
            String[] paths = inputPath.split(",");
            for (String path : paths) {
                if (path.trim().isEmpty()) {
                    continue;
                }
                File f = new File(path);
                out.add(f);
            }
        } else {
            if (!inputPath.trim().isEmpty()) {
                out.add(new File(inputPath));
            }
        }
        return out.toArray(new File[out.size()]);
    }
            
    
    
    public static void main(String[] args) throws Exception {
        if (getArgByName(args, "help") != null) {
            System.out.println("Codename One CSS Compiler\nversion "+version);
            System.out.println("Usage Instructions:\n");
            System.out.println(" java -jar designer.jar -Dcli=true -css -input [inputfile.css] -output [outputfile.res] OPTIONS...\n");
            System.out.println("Options:");
            System.out.println(" -i, -input         Input CSS file path.  Multiple files separated by commas.");
            System.out.println(" -o, -output        Output res file path.");
            System.out.println(" -m, -merge         Path to merge file, used in  case there are multipl input files.");
            System.out.println(" -w, -watch         Run in watch mode.");
            System.out.println("                    Watches input files for changes and automatically recompiles.");
            System.out.println("\nSystem Properties:");
            System.out.println(" cef.dir            The path to the CEF directory.");
            System.out.println("                    Required for generation of image borders.");
            System.out.println(" parent.port        The port number to connect to the parent process for watch mode so that it knows ");
            System.out.println("                    to exit if the parent process ends.");
            return;
            
            
        }
        statelessMode = getArgByName(args, "i", "input") != null;
        String inputPath;
        String outputPath;
        String mergedFile;
        if (statelessMode) {
            System.out.println("Using stateless mode");
            inputPath = getArgByName(args, "i", "input");
            outputPath = getArgByName(args, "o", "output");
            if (outputPath == null) {
                throw new IllegalArgumentException("Output path is required.  Use -o [filepath] or -output [filepath]");
            }
            watchmode = "true".equals(getArgByName(args, "watch"));
            if (watchmode && !CN1Bootstrap.isBootstrapped() && !CN1Bootstrap.isCEFLoaded()) {
                // In watch mode we require CEF to be loaded
                throw new MissingNativeBrowserException();
            }
            mergedFile = getArgByName(args, "m", "merge");
            
            mergeMode = inputPath.contains(",") || mergedFile != null;
            if (mergeMode && mergedFile == null) {
                throw new IllegalArgumentException("When compiling multiple files, you must specify a merge path.  Use -m [filepath] or -merge [filepath]");
            }
           
            
        } else {
            System.out.println("Using stateful mode. Use -help flag to see options for new stateless mode.");
            // We don't want media queries while editing CSS because we need to be 
            // editing the rules raw.
            if (checkWatchMode(args) && !CN1Bootstrap.isBootstrapped() && !CN1Bootstrap.isCEFLoaded()) {
                // In watch mode we require 
                throw new MissingNativeBrowserException();
            }
            Resources.setEnableMediaQueries(false);
            mergeMode = isMergeMode(args);
            inputPath = getInputFile(args);

            mergedFile = mergeMode ? getMergedFile(inputPath) : inputPath;
            

            outputPath = inputPath+".res";

            if (args.length > 1) {
                if ("-watch".equals(args[1])) {
                    watchmode = true;
                    File tmpF = new File(inputPath).getParentFile().getParentFile();
                    tmpF = new File(tmpF, "src");
                    tmpF = new File(tmpF, new File(inputPath).getName()+".res");
                    outputPath = tmpF.getAbsolutePath();
                } else {
                    outputPath = args[1];
                }
            } else {
                File tmpF = new File(inputPath).getParentFile().getParentFile();
                tmpF = new File(tmpF, "src");
                tmpF = new File(tmpF, new File(inputPath).getName()+".res");
                outputPath = tmpF.getAbsolutePath();

            }
            if (args.length > 2 && "-watch".equals(args[2])) {
                watchmode = true;
            }
        }
        
        
        if (!Display.isInitialized()) {
            JavaSEPort.setShowEDTViolationStacks(false);
            JavaSEPort.blockMonitors();
            JavaSEPort.setShowEDTWarnings(false);
            JFrame f = new JFrame();
            Display.init(f.getContentPane());
            int w = 640;
            int h = 480;
            f.getContentPane().setPreferredSize(new java.awt.Dimension(w, h));
            f.getContentPane().setMinimumSize(new java.awt.Dimension(w, h));
            f.getContentPane().setMaximumSize(new java.awt.Dimension(w, h));
            f.pack();
            //f.setVisible(true);
            
        }
        
        File[] inputFiles = getInputFiles(inputPath);
        if (inputFiles.length == 0) {
            throw new IllegalArgumentException("CSS Compiler requires at least one input file");
        }
        if (mergeMode) {
            System.out.println("Updating merge file "+mergedFile);
            updateMergeFile(inputFiles, new File(mergedFile));
        }

        File outputFile = new File(outputPath);
        if (watchmode && watchThread == null) {
            System.out.println("Starting watch thread to watch "+Arrays.toString(inputFiles));
            watchThread = new Thread(new Runnable() {

                @Override
                public void run() {
                    
                    // When run in watch mode, a parent process can provide a port
                    // number that the cli compiler can connect to in order to detect
                    // if the parent process is "dead".  If this socket disconnects 
                    // for any reason, we know the parent process is dead and we can
                    // exit.
                    final String parentPort = System.getProperty("parent.port", null);
                    if (parentPort != null) {
                        Thread pulseThread = new Thread(new Runnable() {

                            @Override
                            public void run() {
                                try {
                                    Socket sock = new Socket("127.0.0.1", Integer.parseInt(parentPort));
                                    sock.setKeepAlive(true);
                                    InputStream is = sock.getInputStream();
                                    while (is.read() >= 0) {
                                        // Still alive
                                    }
                                    
                                } catch (IOException ex) {
                                    Logger.getLogger(CN1CSSCLI.class.getName()).log(Level.SEVERE, null, ex);
                                }
                                System.exit(0);
                            }
                            
                        });
                        pulseThread.setDaemon(true);
                        pulseThread.start();
                    }
                    
                    
                    PollingFileWatcher watcher = new PollingFileWatcher(inputFiles, 1000);
                    while (true) {
                        try {
                            watcher.poll();
                            try {
                                System.out.println("Changed detected in "+Arrays.toString(inputFiles)+".  Recompiling");
                                if (mergeMode) {
                                    updateMergeFile(inputFiles, new File(mergedFile));
                                    compile(new File(mergedFile), outputFile);
                                } else {
                                    compile(inputFiles[0], outputFile);
                                }

                                System.out.println("::refresh::"); // Signal to CSSWatcher in Simulator that it should refresh
                            } catch (Throwable t) {
                                System.err.println("Compile of "+Arrays.toString(inputFiles)+" failed");
                                t.printStackTrace();    
                            }
                        } catch (InterruptedException ex) {
                            Logger.getLogger(CN1CSSCLI.class.getName()).log(Level.SEVERE, null, ex);
                        }

                    }

                    
                    
                }
                
            });
            
        }

        try {
            if (mergeMode) {
                System.out.println("Compiling "+mergedFile+" to "+outputFile);
                compile(new File(mergedFile), outputFile);
            } else {
                compile(inputFiles[0], outputFile);
            }
            System.out.println("CSS file successfully compiled.  "+outputFile);
            
        } catch (Throwable t) {
            if (!CN1Bootstrap.isBootstrapped() && t instanceof MissingNativeBrowserException) {
                // Rethrow MissingNativeBrowserException so that it can be handled in main
                // to attempt to re-add
                throw t;
            }
            t.printStackTrace();
            if (!watchmode) {
                System.exit(1);
            }
        }
        
        if (!watchmode) {
            System.exit(0);
        } else {
            if (watchThread != null && !watchThread.isAlive()) {
                watchThread.start();
                watchThread.join();
            }
            
        }
        
    }
    
    
    
    
    private static void compile(File inputFile, File outputFile) throws IOException {
        File baseDir = inputFile.getParentFile().getParentFile();
        File checksumsFile = getChecksumsFile(baseDir);
        if (!checksumsFile.exists()) {
            saveChecksums(baseDir, new HashMap());
        }
        if (!checksumsFile.exists()) {
            throw new RuntimeException("Failed to create checksums file");
        }
        FileChannel channel = new RandomAccessFile(checksumsFile, "rw").getChannel();
        FileLock lock = channel.lock();
        try {
            Map checksums = loadChecksums(baseDir);
            if (outputFile.exists() && !isMavenProject(inputFile)) {
                String outputFileChecksum = getMD5Checksum(outputFile.getAbsolutePath());
                String previousChecksum = checksums.get(inputFile.getName());
                if (previousChecksum == null || !previousChecksum.equals(outputFileChecksum)) {
                    File backups = new File(inputFile.getParentFile(), ".backups");
                    backups.mkdirs();
                    File bak = new File(backups, outputFile.getName()+"."+System.currentTimeMillis()+".bak");
                    Files.copy(outputFile.toPath(), bak.toPath(), StandardCopyOption.REPLACE_EXISTING);
                    System.out.println(outputFile+" has been modified since it was last compiled.  Making copy at "+bak);
                    outputFile.delete();
                }
                
            }
           
            if (outputFile.exists() && inputFile.lastModified() <= outputFile.lastModified()) {
                System.out.println("File has not changed since last compile.");
                return;
            }
                    

            try {
                URL url = inputFile.toURI().toURL();
                //CSSTheme theme = CSSTheme.load(CSSTheme.class.getResource("test.css"));
                CSSTheme theme = CSSTheme.load(url);
                theme.cssFile = inputFile;
                theme.resourceFile = outputFile;
                JavaSEPort.setBaseResourceDir(outputFile.getParentFile());
                WebViewProvider webViewProvider = new WebViewProvider() {

                    @Override
                    public BrowserComponent getWebView() {
                        if (web == null) {
                            if (!CN1Bootstrap.isCEFLoaded()/* && !CN1Bootstrap.isJavaFXLoaded()*/) {
                                // In theory having JavaFX should work, but I'm having problems getting the snapshotter
                                // to output the correct bounds so we are killing FX support.  CEF only.
                                throw new MissingNativeBrowserException();
                            }
                            if (!CN.isEdt()) {
                                CN.callSerially(()->{
                                    getWebView();
                                });
                                int counter = 0;
                                while (web == null && counter++ < 50) {
                                    Util.sleep(100);
                                }
                                return web;
                            }
                            web = new BrowserComponent();
                            ComponentSelector.select("*", web).add(web, true).selectAllStyles()
                                    .setBgTransparency(0)
                                    .setMargin(0)
                                    .setPadding(0)
                                    .setBorder(Border.createEmpty())
                                    .each(new ComponentClosure() {
                                @Override
                                public void call(Component c) {
                                    c.setOpaque(false);
                                }
                                        
                                
                                    });
                            web.setOpaque(false);
                            Form f = new Form();
                            f.getContentPane().getStyle().setBgColor(0xff0000);
                            f.getContentPane().getStyle().setBgTransparency(0xff);
                            if (f.getToolbar() == null) {
                                f.setToolbar(new com.codename1.ui.Toolbar());
                            }
                            f.getToolbar().hideToolbar();
                            f.setLayout(new com.codename1.ui.layouts.BorderLayout());
                            f.add(CN.CENTER, web);
                            f.show();
                            
                            
                            
                            
                        } 
                        
                        return web;
                    }

                };


                File cacheFile = new File(theme.cssFile.getParentFile(), theme.cssFile.getName()+".checksums");
                if (outputFile.exists() && cacheFile.exists()) {
                    theme.loadResourceFile();
                    theme.loadSelectorCacheStatus(cacheFile);
                }

                theme.createImageBorders(webViewProvider);
                theme.updateResources();
                theme.save(outputFile);
                
                theme.saveSelectorChecksums(cacheFile);
                
                String checksum = getMD5Checksum(outputFile.getAbsolutePath());
                checksums.put(inputFile.getName(), checksum);
                saveChecksums(baseDir, checksums);
            
            } catch (MalformedURLException ex) {
                Logger.getLogger(CN1CSSCLI.class.getName()).log(Level.SEVERE, null, ex);
            } 
        } finally {
            if (lock != null) {
                lock.release();
            }
            if (channel != null) {
                channel.close();
            }
            
            
        }
    }
    
    private static byte[] createChecksum(String filename) throws IOException  {
        try {
            InputStream fis =  new FileInputStream(filename);
            
            byte[] buffer = new byte[1024];
            MessageDigest complete = MessageDigest.getInstance("MD5");
            int numRead;
            
            do {
                numRead = fis.read(buffer);
                if (numRead > 0) {
                    complete.update(buffer, 0, numRead);
                }
            } while (numRead != -1);
            
            fis.close();
            return complete.digest();
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
   }
    // see this How-to for a faster way to convert
   // a byte array to a HEX string
   private static String getMD5Checksum(String filename) throws IOException {
       byte[] b = createChecksum(filename);
       String result = "";

       for (int i=0; i < b.length; i++) {
           result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
       }
       return result;
   }
   
   private static Map loadChecksums(File baseDir) {
       File checkSums = getChecksumsFile(baseDir);
       if (!checkSums.exists()){
           return new HashMap();
       }
       HashMap out = new HashMap();
       try (FileInputStream fis = new FileInputStream(checkSums)){
            Scanner scanner = new Scanner(fis);

            //now read the file line by line...
            int lineNum = 0;
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                lineNum++;
                String[] parts = line.split(":");
                if (parts.length == 2) {
                    out.put(parts[0], parts[1]);
                }
            }
            return out;
        } catch(Exception e) { 
            //handle this
            return out;
        }
       
   }
   
   private static File getChecksumsFile(File baseDir) {
        try {
            if (isMavenProject(baseDir)) {
                return new File(getProjectDir(baseDir), "target" + File.separator + ".cn1_css_checksums");
            }
        } catch (Exception ex) {
            Log.e(ex);
        }
        
       return new File(baseDir, ".cn1_css_checksums");
   }
   
   private static void saveChecksums(File baseDir, Map map) throws IOException {
        try (PrintWriter out = new PrintWriter(new FileOutputStream(getChecksumsFile(baseDir)))) {
            for (String key : map.keySet()) {
                out.println(key+":"+map.get(key));
            }
        }
       
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy