001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019
020import java.io.BufferedInputStream;
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileOutputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027import java.util.Map;
028import java.util.zip.ZipEntry;
029import java.util.zip.ZipFile;
030import java.util.zip.ZipInputStream;
031
032import javax.xml.parsers.DocumentBuilder;
033import javax.xml.parsers.DocumentBuilderFactory;
034import javax.xml.parsers.ParserConfigurationException;
035
036import org.nuxeo.runtime.api.Framework;
037import org.w3c.dom.Document;
038import org.w3c.dom.Element;
039import org.w3c.dom.Node;
040import org.xml.sax.SAXException;
041
042/**
043 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
044 */
045public class Archetype {
046
047    private static final int BUFFER_SIZE = 1024 * 64; // 64K
048
049    private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K
050
051    private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K
052
053    static boolean batchMode = false;
054
055    static String outDir = "${artifactId}";
056
057    static File archive;
058
059    public static void main(String[] args) throws Exception {
060        if (args.length == 0) {
061            System.err.println("Syntax Error: you must specify a project template name");
062        }
063        int k = 0;
064        String tpl = args[k];
065        if ("-b".equals(tpl)) {
066            batchMode = true;
067            if (args.length < 2) {
068                System.err.println("Syntax Error: you must specify a project template name");
069            }
070            tpl = args[++k];
071        }
072        k++;
073        archive = new File(tpl);
074        if (args.length > k) {
075            outDir = args[k];
076        }
077        ZipFile zip = new ZipFile(archive);
078        ZipEntry entry = zip.getEntry("archetype.xml");
079        if (entry == null) {
080            System.err.println("Invalid archetype zip.");
081            System.exit(1);
082        }
083        // load archetype definition
084        InputStream in = new BufferedInputStream(zip.getInputStream(entry));
085        Document doc = load(in);
086        zip.close();
087        // process it
088        processArchetype(doc, System.getProperties());
089    }
090
091    private static void expandVars(File file, Map<?, ?> vars) throws IOException {
092        String content = readFile(file);
093        content = expandVars(content, vars);
094        writeFile(file, content);
095    }
096
097    public static void unzip(File zip, File dir) throws IOException {
098        ZipInputStream in = null;
099        try {
100            in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)));
101            unzip(in, dir);
102        } finally {
103            if (in != null) {
104                in.close();
105            }
106        }
107    }
108
109    public static void unzip(ZipInputStream in, File dir) throws IOException {
110        dir.mkdirs();
111        ZipEntry entry = in.getNextEntry();
112        while (entry != null) {
113            // System.out.println("Extracting "+entry.getName());
114            File file = new File(dir, entry.getName());
115            if (entry.isDirectory()) {
116                file.mkdirs();
117            } else {
118                file.getParentFile().mkdirs();
119                copyToFile(in, file);
120            }
121            entry = in.getNextEntry();
122        }
123    }
124
125    public static String getShortName(String name) {
126        int p = name.lastIndexOf('.');
127        if (p > -1) {
128            return name.substring(0, p);
129        }
130        return name;
131    }
132
133    public static String expandVars(String expression, Map<?, ?> properties) {
134        int p = expression.indexOf("${");
135        if (p == -1) {
136            return expression; // do not expand if not needed
137        }
138
139        char[] buf = expression.toCharArray();
140        StringBuilder result = new StringBuilder(buf.length);
141        if (p > 0) {
142            result.append(expression.substring(0, p));
143        }
144        StringBuilder varBuf = new StringBuilder();
145        boolean dollar = false;
146        boolean var = false;
147        for (int i = p; i < buf.length; i++) {
148            char c = buf[i];
149            switch (c) {
150            case '$':
151                dollar = true;
152                break;
153            case '{':
154                if (dollar) {
155                    dollar = false;
156                    var = true;
157                } else {
158                    result.append(c);
159                }
160                break;
161            case '}':
162                if (var) {
163                    var = false;
164                    String varName = varBuf.toString();
165                    varBuf.setLength(0);
166                    // get the variable value
167                    Object varValue = properties.get(varName);
168                    if (varValue != null) {
169                        result.append(varValue.toString());
170                    } else { // let the variable as is
171                        result.append("${").append(varName).append('}');
172                    }
173                } else {
174                    result.append(c);
175                }
176                break;
177            default:
178                if (var) {
179                    varBuf.append(c);
180                } else {
181                    result.append(c);
182                }
183                break;
184            }
185        }
186        return result.toString();
187    }
188
189    public static String readFile(File file) throws IOException {
190        FileInputStream in = null;
191        try {
192            in = new FileInputStream(file);
193            return read(in);
194        } finally {
195            if (in != null) {
196                in.close();
197            }
198        }
199    }
200
201    public static String read(InputStream in) throws IOException {
202        StringBuilder sb = new StringBuilder();
203        byte[] buffer = createBuffer(in.available());
204        try {
205            int read;
206            while ((read = in.read(buffer)) != -1) {
207                sb.append(new String(buffer, 0, read));
208            }
209        } finally {
210            in.close();
211        }
212        return sb.toString();
213    }
214
215    public static void copyToFile(InputStream in, File file) throws IOException {
216        OutputStream out = null;
217        try {
218            out = new FileOutputStream(file);
219            byte[] buffer = createBuffer(in.available());
220            int read;
221            while ((read = in.read(buffer)) != -1) {
222                out.write(buffer, 0, read);
223            }
224        } finally {
225            if (out != null) {
226                out.close();
227            }
228        }
229    }
230
231    private static byte[] createBuffer(int preferredSize) {
232        if (preferredSize < 1) {
233            preferredSize = BUFFER_SIZE;
234        }
235        if (preferredSize > MAX_BUFFER_SIZE) {
236            preferredSize = MAX_BUFFER_SIZE;
237        } else if (preferredSize < MIN_BUFFER_SIZE) {
238            preferredSize = MIN_BUFFER_SIZE;
239        }
240        return new byte[preferredSize];
241    }
242
243    public static Document load(File file) throws ParserConfigurationException, SAXException, IOException {
244        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
245        factory.setNamespaceAware(true);
246        DocumentBuilder builder = factory.newDocumentBuilder();
247        return builder.parse(file);
248    }
249
250    public static Document load(InputStream in) throws ParserConfigurationException, SAXException, IOException {
251        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
252        factory.setNamespaceAware(true);
253        DocumentBuilder builder = factory.newDocumentBuilder();
254        return builder.parse(in);
255    }
256
257    public static void processVars(Element root, Map<Object, Object> vars) throws IOException {
258        Node node = root.getFirstChild();
259        while (node != null) {
260            if (node.getNodeType() == Node.ELEMENT_NODE) {
261                Element el = (Element) node;
262                if ("var".equals(el.getNodeName())) {
263                    String key = el.getAttribute("name");
264                    String label = el.getAttribute("label");
265                    if (label == null) {
266                        label = key;
267                    }
268                    String val = (String) vars.get(key);
269                    String def = el.getAttribute("default");
270                    if (def != null) {
271                        def = expandVars(def, vars);
272                    } else {
273                        def = val;
274                    }
275                    if (!batchMode && val == null && "true".equals(el.getAttribute("prompt"))) {
276                        val = readVar(label, def);
277                    }
278                    if (val == null) {
279                        val = def;
280                    }
281                    vars.put(key, val);
282                }
283            }
284            node = node.getNextSibling();
285        }
286    }
287
288    public static void processResources(Element root, File dir, Map<Object, Object> vars) throws IOException {
289        Node node = root.getFirstChild();
290        while (node != null) {
291            if (node.getNodeType() == Node.ELEMENT_NODE) {
292                Element el = (Element) node;
293                if ("directory".equals(el.getNodeName())) {
294                    String srcName = el.getAttribute("src");
295                    if (srcName == null) {
296                        throw new IllegalArgumentException("directory has no src attribute");
297                    }
298                    String targetName = el.getAttribute("target");
299                    if (targetName == null) {
300                        throw new IllegalArgumentException("directory has no target attribute");
301                    }
302                    srcName = expandVars(srcName, vars);
303                    targetName = expandVars(targetName, vars);
304                    File src = new File(dir, srcName);
305                    File dst = new File(dir, targetName);
306                    System.out.println("Renaming " + src + " to " + dst);
307                    src.renameTo(dst);
308                } else if ("package".equals(el.getNodeName())) {
309                    String srcName = el.getAttribute("src");
310                    if (srcName == null) {
311                        throw new IllegalArgumentException("package has no src attribute");
312                    }
313                    String targetName = el.getAttribute("target");
314                    if (targetName == null) {
315                        throw new IllegalArgumentException("package has no target attribute");
316                    }
317                    srcName = expandVars(srcName, vars);
318                    targetName = expandVars(targetName, vars);
319                    targetName = targetName.replaceAll("\\.", "/");
320                    File src = new File(dir, srcName);
321                    File dst = new File(dir, targetName);
322                    System.out.println("Renaming " + src + " to " + dst);
323                    dst.getParentFile().mkdirs();
324                    src.renameTo(dst);
325                } else if ("template".equals(el.getNodeName())) {
326                    String srcName = el.getAttribute("src");
327                    if (srcName == null) {
328                        throw new IllegalArgumentException("rename has no src attribute");
329                    }
330                    File src = new File(dir, srcName);
331                    System.out.println("Processing " + src);
332                    expandVars(src, vars);
333                }
334            }
335            node = node.getNextSibling();
336        }
337    }
338
339    public static void processArchetype(Document doc, Map<Object, Object> vars) throws IOException {
340        Element root = doc.getDocumentElement();
341        Node node = root.getFirstChild();
342        Element elVars = null;
343        Element elRes = null;
344        while (node != null) {
345            if (node.getNodeType() == Node.ELEMENT_NODE) {
346                Element el = (Element) node;
347                if ("vars".equals(el.getNodeName())) {
348                    elVars = el;
349                } else if ("resources".equals(el.getNodeName())) {
350                    elRes = el;
351                }
352            }
353            node = node.getNextSibling();
354        }
355        if (elVars != null) {
356            processVars(elVars, vars);
357        }
358        // System.out.println("vars: "+System.getProperty("artifactId")+" - "+System.getProperty("groupId")+" = "+System.getProperty("moduleId"));
359        outDir = expandVars(outDir, vars);
360        File out = new File(outDir);
361        if (out.exists()) {
362            System.out.println("Target directory already exists: " + out);
363            System.out.println("Please specify as target a directory to be created. Exiting.");
364            System.exit(1);
365        }
366        unzip(archive, out);
367        new File(out, "archetype.xml").delete();
368        if (elRes != null) {
369            processResources(elRes, out, vars);
370        }
371    }
372
373    public static String readVar(String key, String value) throws IOException {
374        System.out.print(key + (value == null ? ": " : " [" + value + "]: "));
375        StringBuilder buf = new StringBuilder();
376        int c = System.in.read();
377        LOOP: while (c != -1) {
378            if (c == '\n' || c == '\r') {
379                if (buf.length() == 0) {
380                    if (value == null) {
381                        System.out.println(key + ": ");
382                        break LOOP;
383                    } else {
384                        return value;
385                    }
386                }
387                return buf.toString();
388            }
389            buf.append((char) c);
390            c = System.in.read();
391        }
392        return value;
393    }
394
395    public static File unzipArchetype(File zipFile) throws IOException {
396        File file = Framework.createTempFile("nuxeo_archetype_" + zipFile.getName(), ".tmp");
397        Framework.trackFile(file, file);
398        file.delete();
399        file.mkdirs();
400        unzip(zipFile, file);
401        return file;
402    }
403
404    public static void deleteTree(File dir) {
405        emptyDirectory(dir);
406        dir.delete();
407    }
408
409    public static void emptyDirectory(File dir) {
410        File[] files = dir.listFiles();
411        if (files == null) {
412            return;
413        }
414        int len = files.length;
415        for (int i = 0; i < len; i++) {
416            File file = files[i];
417            if (file.isDirectory()) {
418                deleteTree(file);
419            } else {
420                file.delete();
421            }
422        }
423    }
424
425    public static void writeFile(File file, byte[] buf) throws IOException {
426        FileOutputStream fos = null;
427        try {
428            fos = new FileOutputStream(file);
429            fos.write(buf);
430        } finally {
431            if (fos != null) {
432                fos.close();
433            }
434        }
435    }
436
437    public static void writeFile(File file, String buf) throws IOException {
438        writeFile(file, buf.getBytes());
439    }
440
441    public static void launch(String[] args) {
442        // launch an app.
443    }
444
445}