1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.eclipse.org/org/documents/epl-v10.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.ide.eclipse.adt.internal.wizards.templates;
17
18import static com.android.SdkConstants.FD_EXTRAS;
19import static com.android.SdkConstants.FD_TEMPLATES;
20import static com.android.SdkConstants.FD_TOOLS;
21import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TEMPLATE_XML;
22
23import com.android.annotations.NonNull;
24import com.android.annotations.Nullable;
25import com.android.ide.eclipse.adt.AdtPlugin;
26import com.android.ide.eclipse.adt.AdtUtils;
27import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
28import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
29import com.google.common.base.Charsets;
30import com.google.common.collect.Maps;
31import com.google.common.collect.Sets;
32import com.google.common.io.Files;
33
34import org.w3c.dom.Document;
35
36import java.io.File;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.Comparator;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44
45/** Handles locating templates and providing template metadata */
46public class TemplateManager {
47	private static final Set<String> EXCLUDED_CATEGORIES = Sets.newHashSet("Folder", "Google");
48	private static final Set<String> EXCLUDED_FORMFACTORS = Sets.newHashSet("Wear", "TV");
49
50    TemplateManager() {
51    }
52
53    /** @return the root folder containing templates */
54    @Nullable
55    public static File getTemplateRootFolder() {
56        String location = AdtPrefs.getPrefs().getOsSdkFolder();
57        if (location != null) {
58            File folder = new File(location, FD_TOOLS + File.separator + FD_TEMPLATES);
59            if (folder.isDirectory()) {
60                return folder;
61            }
62        }
63
64        return null;
65    }
66
67    /** @return the root folder containing extra templates */
68    @NonNull
69    public static List<File> getExtraTemplateRootFolders() {
70        List<File> folders = new ArrayList<File>();
71        String location = AdtPrefs.getPrefs().getOsSdkFolder();
72        if (location != null) {
73            File extras = new File(location, FD_EXTRAS);
74            if (extras.isDirectory()) {
75                for (File vendor : AdtUtils.listFiles(extras)) {
76                    if (!vendor.isDirectory()) {
77                        continue;
78                    }
79                    for (File pkg : AdtUtils.listFiles(vendor)) {
80                        if (pkg.isDirectory()) {
81                            File folder = new File(pkg, FD_TEMPLATES);
82                            if (folder.isDirectory()) {
83                                folders.add(folder);
84                            }
85                        }
86                    }
87                }
88
89                // Legacy
90                File folder = new File(extras, FD_TEMPLATES);
91                if (folder.isDirectory()) {
92                    folders.add(folder);
93                }
94            }
95        }
96
97        return folders;
98    }
99
100    /**
101     * Returns a template file under the given root, if it exists
102     *
103     * @param root the root folder
104     * @param relativePath the relative path
105     * @return a template file under the given root, if it exists
106     */
107    @Nullable
108    public static File getTemplateLocation(@NonNull File root, @NonNull String relativePath) {
109        File templateRoot = getTemplateRootFolder();
110        if (templateRoot != null) {
111            String rootPath = root.getPath();
112            File templateFile = new File(templateRoot,
113                    rootPath.replace('/', File.separatorChar) + File.separator
114                    + relativePath.replace('/', File.separatorChar));
115            if (templateFile.exists()) {
116                return templateFile;
117            }
118        }
119
120        return null;
121    }
122
123    /**
124     * Returns a template file under one of the available roots, if it exists
125     *
126     * @param relativePath the relative path
127     * @return a template file under one of the available roots, if it exists
128     */
129    @Nullable
130    public static File getTemplateLocation(@NonNull String relativePath) {
131        File templateRoot = getTemplateRootFolder();
132        if (templateRoot != null) {
133            File templateFile = new File(templateRoot,
134                    relativePath.replace('/', File.separatorChar));
135            if (templateFile.exists()) {
136                return templateFile;
137            }
138        }
139
140        return null;
141
142    }
143
144    /**
145     * Returns all the templates with the given prefix
146     *
147     * @param folder the folder prefix
148     * @return the available templates
149     */
150    @NonNull
151    List<File> getTemplates(@NonNull String folder) {
152        List<File> templates = new ArrayList<File>();
153        Map<String, File> templateNames = Maps.newHashMap();
154        File root = getTemplateRootFolder();
155        if (root != null) {
156            File[] files = new File(root, folder).listFiles();
157            if (files != null) {
158                for (File file : files) {
159                    if (file.isDirectory()) { // Avoid .DS_Store etc
160                        templates.add(file);
161                        templateNames.put(file.getName(), file);
162                    }
163                }
164            }
165        }
166
167        // Add in templates from extras/ as well.
168        for (File extra : getExtraTemplateRootFolders()) {
169            File[] files = new File(extra, folder).listFiles();
170            if (files != null) {
171                for (File file : files) {
172                    if (file.isDirectory()) {
173                        File replaces = templateNames.get(file.getName());
174                        if (replaces != null) {
175                            int compare = compareTemplates(replaces, file);
176                            if (compare > 0) {
177                                int index = templates.indexOf(replaces);
178                                if (index != -1) {
179                                    templates.set(index, file);
180                                } else {
181                                    templates.add(file);
182                                }
183                            }
184                        } else {
185                            templates.add(file);
186                        }
187                    }
188                }
189            }
190        }
191
192        // Sort by file name (not path as is File's default)
193        if (templates.size() > 1) {
194            Collections.sort(templates, new Comparator<File>() {
195                @Override
196                public int compare(File file1, File file2) {
197                    return file1.getName().compareTo(file2.getName());
198                }
199            });
200        }
201
202        return templates;
203    }
204
205    /**
206     * Compare two files, and return the one with the HIGHEST revision, and if
207     * the same, most recently modified
208     */
209    private int compareTemplates(File file1, File file2) {
210        TemplateMetadata template1 = getTemplate(file1);
211        TemplateMetadata template2 = getTemplate(file2);
212
213        if (template1 == null) {
214            return 1;
215        } else if (template2 == null) {
216            return -1;
217        } else {
218            int delta = template2.getRevision() - template1.getRevision();
219            if (delta == 0) {
220                delta = (int) (file2.lastModified() - file1.lastModified());
221            }
222            return delta;
223        }
224    }
225
226    /** Cache for {@link #getTemplate()} */
227    private Map<File, TemplateMetadata> mTemplateMap;
228
229    @Nullable
230    TemplateMetadata getTemplate(File templateDir) {
231        if (mTemplateMap != null) {
232            TemplateMetadata metadata = mTemplateMap.get(templateDir);
233            if (metadata != null) {
234                return metadata;
235            }
236        } else {
237            mTemplateMap = Maps.newHashMap();
238        }
239
240        try {
241            File templateFile = new File(templateDir, TEMPLATE_XML);
242            if (templateFile.isFile()) {
243                String xml = Files.toString(templateFile, Charsets.UTF_8);
244                Document doc = DomUtilities.parseDocument(xml, true);
245                if (doc != null && doc.getDocumentElement() != null) {
246                    TemplateMetadata metadata = new TemplateMetadata(doc);
247                    if (EXCLUDED_CATEGORIES.contains(metadata.getCategory()) ||
248                    	EXCLUDED_FORMFACTORS.contains(metadata.getFormFactor())) {
249                    	return null;
250                    }
251                    mTemplateMap.put(templateDir, metadata);
252                    return metadata;
253                }
254            }
255        } catch (IOException e) {
256            AdtPlugin.log(e, null);
257        }
258
259        return null;
260    }
261}
262