// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.debug.core.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.efs.ChromiumScriptFileSystem;
import org.chromium.sdk.Breakpoint;
import org.chromium.sdk.BreakpointTypeExtension;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
/**
* A utility for interaction with the Eclipse workspace.
*/
public class ChromiumDebugPluginUtil {
private static final String CHROMIUM_EXTENSION = "chromium"; //$NON-NLS-1$
public static final Set<String> SUPPORTED_EXTENSIONS =
new HashSet<String>(Arrays.asList(CHROMIUM_EXTENSION, "js", //$NON-NLS-1$
"html", "htm")); //$NON-NLS-1$ //$NON-NLS-2$
public static final List<String> SUPPORTED_EXTENSIONS_SUFFIX_LIST;
static {
SUPPORTED_EXTENSIONS_SUFFIX_LIST = new ArrayList<String>(SUPPORTED_EXTENSIONS.size());
for (String extension : SUPPORTED_EXTENSIONS) {
SUPPORTED_EXTENSIONS_SUFFIX_LIST.add("." + extension); //$NON-NLS-1$
}
}
public static final String JS_DEBUG_PROJECT_NATURE = "org.chromium.debug.core.jsnature"; //$NON-NLS-1$
public static final String CHROMIUM_EXTENSION_SUFFIX = "." + CHROMIUM_EXTENSION; //$NON-NLS-1$
private static final String PROJECT_EXPLORER_ID = "org.eclipse.ui.navigator.ProjectExplorer"; //$NON-NLS-1$
/**
* Brings up the "Project Explorer" view in the active workbench window.
*/
public static void openProjectExplorerView() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (window == null) {
if (workbench.getWorkbenchWindowCount() == 1) {
window = workbench.getWorkbenchWindows()[0];
}
}
if (window != null) {
try {
window.getActivePage().showView(PROJECT_EXPLORER_ID);
} catch (PartInitException e) {
// ignore
}
}
}
});
}
/**
* Creates an empty workspace project with the name starting with the given projectNameBase.
* Created project is guaranteed to be new in EFS, but workspace may happen to
* already have project with such url (left uncleaned from previous runs). Such project
* silently gets deleted.
* @param projectNameBase project name template
* @return the newly created project, or {@code null} if the creation failed
*/
public static IProject createEmptyProject(String projectNameBase) {
ProjectCheckData projectProject;
try {
for (int uniqueNumber = 0; ; uniqueNumber++) {
String projectNameTry;
if (uniqueNumber == 0) {
projectNameTry = projectNameBase;
} else {
projectNameTry = projectNameBase + " (" + uniqueNumber + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
projectProject = checkProjectName(projectNameTry);
if (projectProject != null) {
break;
}
}
} catch (CoreException e) {
ChromiumDebugPlugin.log(e);
return null;
}
IProject project = projectProject.getProject();
if (project.exists()) {
try {
project.delete(true, null);
} catch (CoreException e) {
ChromiumDebugPlugin.log(e);
return null;
}
}
IProjectDescription description =
ResourcesPlugin.getWorkspace().newProjectDescription(project.getName());
description.setLocationURI(projectProject.getProjectUri());
description.setNatureIds(new String[] { JS_DEBUG_PROJECT_NATURE });
try {
project.create(description, null);
project.open(null);
return project;
} catch (CoreException e) {
ChromiumDebugPlugin.log(e);
return null;
}
}
private interface ProjectCheckData {
IProject getProject();
URI getProjectUri();
}
/**
* Checks whether debug virtual project can be created.
* @param projectNameTry desired project name
* @return project project with parameters data or null if project with desired name cannot be
* created
*/
private static ProjectCheckData checkProjectName(String projectNameTry) throws CoreException {
final IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectNameTry);
if (project.exists()) {
URI projectURI = project.getLocationURI();
if (!ChromiumScriptFileSystem.isChromiumDebugURI(projectURI)) {
// This is not our project. Do not touch it.
return null;
}
}
IPath newPath = project.getFullPath();
final URI projectUriTry = ChromiumScriptFileSystem.getFileStoreUri(newPath);
IFileStore projectStore = EFS.getStore(projectUriTry);
if (projectStore.fetchInfo().exists()) {
return null;
}
return new ProjectCheckData() {
public IProject getProject() {
return project;
}
public URI getProjectUri() {
return projectUriTry;
}
};
}
/**
* Removes virtual project which was created for debug session. Does its job
* asynchronously.
*/
public static void deleteVirtualProjectAsync(final IProject debugProject) {
Job job = new Job("Remove virtual project") {
@Override
protected IStatus run(IProgressMonitor monitor) {
URI projectUri = debugProject.getLocationURI();
try {
IFileStore projectStore = EFS.getStore(projectUri);
if (projectStore.fetchInfo().exists()) {
projectStore.delete(EFS.NONE, null);
}
debugProject.delete(true, null);
} catch (CoreException e) {
ChromiumDebugPlugin.log(e);
return new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
"Failed to delete virtual project");
}
return Status.OK_STATUS;
}
};
job.schedule();
}
/**
* @param projectName to check for existence
* @return whether the project named projectName exists.
*/
public static boolean projectExists(String projectName) {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IProject proj = ws.getRoot().getProject(projectName);
return proj.exists();
}
/**
* Creates an empty file with the given filename in the given project.
*
* @param container to create the file in
* @param filename the base short file name to create (will be sanitized for
* illegal chars and, in the case of a name clash, suffixed with "(N)")
* @return the result of IFile.getName(), or {@code null} if the creation
* has failed
*/
public static IFile createFile(IProject container, String filename) {
return createFile(container, FileContainerHandler.FOR_PROJECT, filename);
}
/**
* Creates an empty file with the given filename in the given project.
*
* @param container to create the file in
* @param filename the base short file name to create (will be sanitized for
* illegal chars and, in the case of a name clash, suffixed with "(N)")
* @return the result of IFile.getName(), or {@code null} if the creation
* has failed
*/
public static IFile createFile(IFolder container, String filename) {
return createFile(container, FileContainerHandler.FOR_FOLDER, filename);
}
private static <C> IFile createFile(final C container, final FileContainerHandler<C> handler,
String filename) {
String patchedName = FILE_NAME_BAD_CHARS.matcher(filename).replaceAll("_");
// In Linux and Mac it should be enough. However, lets check more.
if (!ResourcesPlugin.getWorkspace().validateName(patchedName, IResource.FILE).isOK()) {
patchedName = FILE_NAME_BAD_CHARS_FALLBACK.matcher(filename).replaceAll("_");
}
UniqueKeyGenerator.Factory<IFile> factory =
new UniqueKeyGenerator.Factory<IFile>() {
public IFile tryCreate(String uniqueName) {
String filePathname = uniqueName + CHROMIUM_EXTENSION_SUFFIX;
IFile file = handler.getFile(container, filePathname);
if (file.exists()) {
return null;
}
try {
file.create(new ByteArrayInputStream("".getBytes()), false, null); //$NON-NLS-1$
} catch (CoreException e) {
IStatus status = e.getStatus();
if (status instanceof IResourceStatus) {
if (status.getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
// File with different name case already exists.
// For our case it's the same as "file.exists()".
return null;
}
}
throw new RuntimeException(e);
}
return file;
}
};
// Can we have 1000 same-named files?
return UniqueKeyGenerator.createUniqueKey(patchedName, 1000, factory);
}
private static final Pattern FILE_NAME_BAD_CHARS = Pattern.compile("[/\\x00]");
private static final Pattern FILE_NAME_BAD_CHARS_FALLBACK = Pattern.compile("[^\\w\\._-]");
/**
* Helper class that provides polymorphic access to {@link IProject} and {@link IContainer}.
* Used for uniform access to "getFile" methods from both interfaces.
*/
private static abstract class FileContainerHandler<T> {
abstract IFile getFile(T container, String name);
static final FileContainerHandler<IProject> FOR_PROJECT = new FileContainerHandler<IProject>() {
@Override IFile getFile(IProject project, String name) {
return project.getFile(name);
}
};
static final FileContainerHandler<IFolder> FOR_FOLDER = new FileContainerHandler<IFolder>() {
@Override IFile getFile(IFolder folder, String name) {
return folder.getFile(name);
}
};
}
/**
* Writes data into a resource with the given resourceName residing in the
* source folder of the given project. The previous file content is lost.
* Temporarily resets the "read-only" file attribute if one is present.
*
* @param file to set contents for
* @param data to write into the file
* @throws CoreException
*/
public static void writeFile(IFile file, String data) throws CoreException {
if (file != null && file.exists()) {
ResourceAttributes resourceAttributes = file.getResourceAttributes();
if (resourceAttributes.isReadOnly()) {
resourceAttributes.setReadOnly(false);
file.setResourceAttributes(resourceAttributes);
}
file.setContents(new ByteArrayInputStream(data.getBytes()), IFile.FORCE, null);
resourceAttributes.setReadOnly(true);
file.setResourceAttributes(resourceAttributes);
}
}
public static boolean isInteger(String value) {
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* The container where the script sources should be put.
*
* @param project where the launch configuration stores the scripts
* @return the script source container
*/
public static IContainer getSourceContainer(IProject project) {
return project;
}
public static byte[] readFileContents(IFile file) throws IOException, CoreException {
InputStream inputStream = file.getContents();
try {
return readBytes(inputStream);
} finally {
inputStream.close();
}
}
public static byte[] readBytes(InputStream inputStream) throws IOException {
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] array = new byte[1024];
while (true) {
int len = bufferedInputStream.read(array);
if (len == -1) {
break;
}
output.write(array, 0, len);
}
return output.toByteArray();
}
public static final Breakpoint.Target.Visitor<String> BREAKPOINT_TARGET_TO_STRING =
new BreakpointTypeExtension.ScriptRegExpSupport.Visitor<String>() {
@Override public String visitScriptName(String scriptName) {
return "script_name=" + scriptName;
}
@Override public String visitScriptId(Object scriptId) {
return "script_id=" + scriptId;
}
@Override public String visitRegExp(String regExp) {
return "RegExp=" + regExp;
}
@Override public String visitUnknown(Breakpoint.Target target) {
return "Unknown target: + " + target;
}
};
public static <T> T throwUnsupported() {
throw new UnsupportedOperationException();
}
private ChromiumDebugPluginUtil() {
// not instantiable
}
}