| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.idea.templates; |
| |
| import com.android.sdklib.AndroidVersion; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.SdkVersionInfo; |
| import com.android.tools.idea.npw.WizardUtils; |
| import com.android.utils.SparseArray; |
| import com.google.common.base.Charsets; |
| import com.google.common.io.Files; |
| import com.intellij.ide.impl.ProjectPaneSelectInTarget; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.fileTypes.StdFileTypes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.ThrowableComputable; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import org.jetbrains.android.sdk.AndroidSdkData; |
| import org.jetbrains.android.sdk.AndroidSdkUtils; |
| import org.jetbrains.android.uipreview.AndroidEditorSettings; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| |
| /** Utility methods for ADT */ |
| @SuppressWarnings("restriction") // WST API |
| public class TemplateUtils { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.templates.DomUtilities"); |
| |
| /** |
| * Creates a Java class name out of the given string, if possible. For |
| * example, "My Project" becomes "MyProject", "hello" becomes "Hello", |
| * "Java's" becomes "Java", and so on. |
| * |
| * @param string the string to be massaged into a Java class |
| * @return the string as a Java class, or null if a class name could not be |
| * extracted |
| */ |
| @Nullable |
| public static String extractClassName(@NotNull String string) { |
| StringBuilder sb = new StringBuilder(string.length()); |
| int n = string.length(); |
| |
| int i = 0; |
| for (; i < n; i++) { |
| char c = Character.toUpperCase(string.charAt(i)); |
| if (Character.isJavaIdentifierStart(c)) { |
| sb.append(c); |
| i++; |
| break; |
| } |
| } |
| if (sb.length() > 0) { |
| for (; i < n; i++) { |
| char c = string.charAt(i); |
| if (Character.isJavaIdentifierPart(c)) { |
| sb.append(c); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Strips the given suffix from the given string, provided that the string ends with |
| * the suffix. |
| * |
| * @param string the full string to strip from |
| * @param suffix the suffix to strip out |
| * @return the string without the suffix at the end |
| */ |
| public static String stripSuffix(@NotNull String string, @NotNull String suffix) { |
| if (string.endsWith(suffix)) { |
| return string.substring(0, string.length() - suffix.length()); |
| } |
| |
| return string; |
| } |
| |
| /** |
| * Strips the given suffix from the given file, provided that the file name ends with |
| * the suffix. |
| * |
| * @param file the file to strip from |
| * @param suffix the suffix to strip out |
| * @return the file without the suffix at the end |
| */ |
| public static File stripSuffix(@NotNull File file, @NotNull String suffix) { |
| if (file.getName().endsWith(suffix)) { |
| String name = file.getName(); |
| name = name.substring(0, name.length() - suffix.length()); |
| File parent = file.getParentFile(); |
| if (parent != null) { |
| return new File(parent, name); |
| } else { |
| return new File(name); |
| } |
| } |
| |
| return file; |
| } |
| |
| /** |
| * Converts a CamelCase word into an underlined_word |
| * |
| * @param string the CamelCase version of the word |
| * @return the underlined version of the word |
| */ |
| public static String camelCaseToUnderlines(String string) { |
| if (string.isEmpty()) { |
| return string; |
| } |
| |
| StringBuilder sb = new StringBuilder(2 * string.length()); |
| int n = string.length(); |
| boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0)); |
| for (int i = 0; i < n; i++) { |
| char c = string.charAt(i); |
| boolean isUpperCase = Character.isUpperCase(c); |
| if (isUpperCase && !lastWasUpperCase) { |
| sb.append('_'); |
| } |
| lastWasUpperCase = isUpperCase; |
| c = Character.toLowerCase(c); |
| sb.append(c); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Converts an underlined_word into a CamelCase word |
| * |
| * @param string the underlined word to convert |
| * @return the CamelCase version of the word |
| */ |
| public static String underlinesToCamelCase(String string) { |
| StringBuilder sb = new StringBuilder(string.length()); |
| int n = string.length(); |
| |
| int i = 0; |
| boolean upcaseNext = true; |
| for (; i < n; i++) { |
| char c = string.charAt(i); |
| if (c == '_') { |
| upcaseNext = true; |
| } else { |
| if (upcaseNext) { |
| c = Character.toUpperCase(c); |
| } |
| upcaseNext = false; |
| sb.append(c); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns a list of known API names |
| * |
| * @return a list of string API names, starting from 1 and up through the |
| * maximum known versions (with no gaps) |
| */ |
| public static String[] getKnownVersions() { |
| final AndroidSdkData sdkData = AndroidSdkUtils.tryToChooseAndroidSdk(); |
| assert sdkData != null; |
| int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API; |
| IAndroidTarget[] targets = sdkData.getTargets(); |
| SparseArray<IAndroidTarget> apiTargets = null; |
| for (IAndroidTarget target : targets) { |
| if (target.isPlatform()) { |
| AndroidVersion version = target.getVersion(); |
| if (!version.isPreview()) { |
| int apiLevel = version.getApiLevel(); |
| max = Math.max(max, apiLevel); |
| if (apiLevel > SdkVersionInfo.HIGHEST_KNOWN_API) { |
| if (apiTargets == null) { |
| apiTargets = new SparseArray<IAndroidTarget>(); |
| } |
| apiTargets.put(apiLevel, target); |
| } |
| } |
| } |
| } |
| |
| String[] versions = new String[max]; |
| for (int api = 1; api <= max; api++) { |
| String name = SdkVersionInfo.getAndroidName(api); |
| if (name == null) { |
| if (apiTargets != null) { |
| IAndroidTarget target = apiTargets.get(api); |
| if (target != null) { |
| name = AndroidSdkUtils.getTargetLabel(target); |
| } |
| } |
| if (name == null) { |
| name = String.format("API %1$d", api); |
| } |
| } |
| versions[api-1] = name; |
| } |
| |
| return versions; |
| } |
| |
| /** |
| * Lists the files of the given directory and returns them as an array which |
| * is never null. This simplifies processing file listings from for each |
| * loops since {@link File#listFiles} can return null. This method simply |
| * wraps it and makes sure it returns an empty array instead if necessary. |
| * |
| * @param dir the directory to list |
| * @return the children, or empty if it has no children, is not a directory, |
| * etc. |
| */ |
| @NotNull |
| public static File[] listFiles(@Nullable File dir) { |
| return WizardUtils.listFiles(dir); |
| } |
| |
| /** |
| * Returns the element children of the given element |
| * |
| * @param element the parent element |
| * @return a list of child elements, possibly empty but never null |
| */ |
| @NotNull |
| public static List<Element> getChildren(@NotNull Element element) { |
| // Convenience to avoid lots of ugly DOM access casting |
| NodeList children = element.getChildNodes(); |
| // An iterator would have been more natural (to directly drive the child list |
| // iteration) but iterators can't be used in enhanced for loops... |
| List<Element> result = new ArrayList<Element>(children.getLength()); |
| for (int i = 0, n = children.getLength(); i < n; i++) { |
| Node node = children.item(i); |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| Element child = (Element) node; |
| result.add(child); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Opens the specified files in the editor |
| * |
| * @param project The project which contains the given file. |
| * @param files The files on disk. |
| * @param select If true, select the last (topmost) file in the project view |
| * @return true if all files were opened |
| */ |
| public static boolean openEditors(@NotNull Project project, @NotNull List<File> files, boolean select) { |
| if (files.size() > 0) { |
| boolean result = true; |
| VirtualFile last = null; |
| for (File file : files) { |
| if (file.exists()) { |
| VirtualFile vFile = VfsUtil.findFileByIoFile(file, true /** refreshIfNeeded */); |
| if (vFile != null) { |
| result &= openEditor(project, vFile); |
| last = vFile; |
| } |
| else { |
| result = false; |
| } |
| } |
| } |
| |
| if (select && last != null) { |
| selectEditor(project, last); |
| } |
| |
| return result; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Opens the specified file in the editor |
| * |
| * @param project The project which contains the given file. |
| * @param vFile The file to open |
| * @return |
| */ |
| public static boolean openEditor(@NotNull Project project, @NotNull VirtualFile vFile) { |
| OpenFileDescriptor descriptor; |
| if (vFile.getFileType() == StdFileTypes.XML && AndroidEditorSettings.getInstance().getGlobalState().isPreferXmlEditor()) { |
| descriptor = new OpenFileDescriptor(project, vFile, 0); |
| } else { |
| descriptor = new OpenFileDescriptor(project, vFile); |
| } |
| return !FileEditorManager.getInstance(project).openEditor(descriptor, true).isEmpty(); |
| } |
| |
| /** |
| * Selects the specified file in the project view. |
| * <b>Note:</b> Must be called with read access. |
| * |
| * @param project the project |
| * @param file the file to select |
| */ |
| public static void selectEditor(Project project, VirtualFile file) { |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| PsiFile psiFile = PsiManager.getInstance(project).findFile(file); |
| if (psiFile != null) { |
| ProjectPaneSelectInTarget selectAction = new ProjectPaneSelectInTarget(project); |
| selectAction.select(psiFile, false); |
| } |
| } |
| |
| /** |
| * Reads the given file as text. |
| * @param file The file to read. Must be an absolute reference. |
| * @param warnIfNotExists if true, logs a warning if the file does not exist. |
| * @return the contents of the file as text |
| */ |
| @Nullable |
| public static String readTextFile(@NotNull File file, boolean warnIfNotExists) { |
| assert file.isAbsolute(); |
| try { |
| return Files.toString(file, Charsets.UTF_8); |
| } catch (IOException e) { |
| if (warnIfNotExists) { |
| LOG.warn(e); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Reads the given file as text. |
| * @param file The file to read. Must be an absolute reference. |
| * @return the contents of the file as text |
| */ |
| @Nullable |
| public static String readTextFile(@NotNull File file) { |
| return readTextFile(file, true); |
| } |
| |
| /** |
| * Reads the given file as text (or the current contents of the edited buffer of the file, if open and not saved.) |
| * @param file The file to read. |
| * @return the contents of the file as text, or null if for some reason it couldn't be read |
| */ |
| @Nullable |
| public static String readTextFile(@NotNull final Project project, @NotNull final VirtualFile file) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Nullable |
| @Override |
| public String compute() { |
| final PsiFile psiFile = PsiManager.getInstance(project).findFile(file); |
| if (psiFile == null) { |
| return null; |
| } |
| else { |
| return psiFile.getText(); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Replaces the contents of the given file with the given string. Outputs |
| * text in UTF-8 character encoding. The file is created if it does not |
| * already exist. |
| */ |
| public static void writeFile(@NotNull Object requestor, @Nullable String contents, @NotNull File to) throws IOException { |
| if (contents == null) { |
| return; |
| } |
| VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(to); |
| if (vf == null) { |
| // Creating a new file |
| VirtualFile parentDir = checkedCreateDirectoryIfMissing(to.getParentFile()); |
| vf = parentDir.createChildData(requestor, to.getName()); |
| } |
| Document document = FileDocumentManager.getInstance().getDocument(vf); |
| if (document != null) { |
| document.setText(contents.replaceAll("\r\n", "\n")); |
| FileDocumentManager.getInstance().saveDocument(document); |
| } |
| else { |
| vf.setBinaryContent(contents.getBytes(Charsets.UTF_8), -1, -1, requestor); |
| } |
| } |
| |
| /** |
| * Creates a directory for the given file and returns the VirtualFile object. |
| * |
| * @return virtual file object for the given path. It can never be null. |
| */ |
| @NotNull |
| public static VirtualFile checkedCreateDirectoryIfMissing(final @NotNull File directory) throws IOException { |
| return WriteCommandAction.runWriteCommandAction(null, new ThrowableComputable<VirtualFile, IOException>() { |
| @Override |
| public VirtualFile compute() throws IOException { |
| VirtualFile dir = VfsUtil.createDirectoryIfMissing(directory.getAbsolutePath()); |
| if (dir == null) { |
| throw new IOException("Unable to create " + directory.getAbsolutePath()); |
| } |
| else { |
| return dir; |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Returns true iff the given file has the given extension (with or without .) |
| */ |
| public static boolean hasExtension(File file, String extension) { |
| String noDotExtension = extension.startsWith(".") ? extension.substring(1) : extension; |
| return Files.getFileExtension(file.getName()).equalsIgnoreCase(noDotExtension); |
| } |
| |
| } |