blob: c8cee8468a796195cdbe8ed8ad3215b13b8073c1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.util;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.pde.core.plugin.*;
import org.eclipse.pde.internal.core.TargetPlatformHelper;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginBase;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.pde.internal.ui.wizards.product.ISplashHandlerConstants;
import org.eclipse.pde.internal.ui.wizards.product.UpdateSplashHandlerAction;
import org.eclipse.pde.internal.ui.wizards.templates.ControlStack;
import org.eclipse.pde.ui.templates.ITemplateSection;
import org.eclipse.pde.ui.templates.IVariableProvider;
import org.osgi.framework.Bundle;
/**
* TemplateFileGenerator
*
*/
public class TemplateFileGenerator implements IVariableProvider {
// TODO: MP: SPLASH: Merge this utility back with template (maybe as an abstract base class?) - Extracted from org.eclipse.pde.ui.templates.AbstractTemplateSection
// TODO: MP: SPLASH: Major code-clean-up required
/**
* The key for the main plug-in class of the plug-in that the template is
* used for (value="pluginClass"). The return value is a fully-qualified class name.
*/
public static final String KEY_PLUGIN_CLASS = "pluginClass"; //$NON-NLS-1$
/**
* The key for the simple class name of a bundle activator (value="activator")
*
* @since 3.3
*/
public static final String KEY_ACTIVATOR_SIMPLE = "activator"; //$NON-NLS-1$
/**
* The key for the plug-in id of the plug-in that the template is used for
* (value="pluginId").
*/
public static final String KEY_PLUGIN_ID = "pluginId"; //$NON-NLS-1$
/**
* The key for the plug-in name of the plug-in that the template is used for
* (value="pluginName").
*/
public static final String KEY_PLUGIN_NAME = "pluginName"; //$NON-NLS-1$
/**
* The key for the package name that will be created by this teamplate
* (value="packageName").
*/
public static final String KEY_PACKAGE_NAME = "packageName"; //$NON-NLS-1$
private IProject fProject;
private IPluginModelBase fModel;
private String fPluginID;
private String fPackage;
private String fClass;
private String fTemplate;
/**
* @param project
* @param model
*/
public TemplateFileGenerator(IProject project, IPluginModelBase model, String pluginID, String targetPackage, String targetClass, String template) {
fProject = project;
fModel = model;
fPluginID = pluginID;
fPackage = targetPackage;
fClass = targetClass;
fTemplate = template;
}
/**
* Generates files as part of the template execution. The default
* implementation uses template location as a root of the file templates.
* {@link #generateFiles(IProgressMonitor monitor, URL locationUrl)} on how
* the location gets processed.
*
* @param monitor
* progress monitor to use to indicate generation progress
*/
public void generateFiles(IProgressMonitor monitor) throws CoreException {
// Generate files using the default template location
generateFiles(monitor, getTemplateLocation());
// Generate files using the shared branding location (for splash screen)
Bundle templateBundle = Platform.getBundle("org.eclipse.pde.ui.templates"); //$NON-NLS-1$
if (templateBundle == null) {
return;
}
generateFiles(monitor, templateBundle.getEntry("branding/")); //$NON-NLS-1$
}
public URL getTemplateLocation() {
Bundle bundle = Platform.getBundle("org.eclipse.pde.ui.templates"); //$NON-NLS-1$
if (bundle == null) {
return null;
}
try {
String[] candidates = getDirectoryCandidates();
for (String directoryCandidate : candidates) {
if (bundle.getEntry(directoryCandidate) != null) {
URL candidate = new URL(bundle.getEntry("/"), directoryCandidate); //$NON-NLS-1$
return candidate;
}
}
} catch (MalformedURLException e) {
}
return null;
}
/**
* Generates files as part of the template execution.
* The files found in the location are processed in the following way:
* <ul>
* <li>Files and folders found in the directory <samp>bin </samp> are
* copied into the target project without modification.</li>
* <li>Files found in the directory <samp>java </samp> are copied into the
* Java source folder by creating the folder structure that corresponds to
* the package name (variable <samp>packageName </samp>). Java files are
* subject to conditional generation and variable replacement.</li>
* <li>All other files and folders are copied directly into the target
* folder with the conditional generation and variable replacement for
* files. Variable replacement also includes file names.</li>
* </ul>
*
* @since 3.3
* @param monitor
* progress monitor to use to indicate generation progress
* @param locationUrl a url pointing to a file/directory that will be copied into the template
*/
protected void generateFiles(IProgressMonitor monitor, URL locationUrl) throws CoreException {
monitor.setTaskName(PDEUIMessages.AbstractTemplateSection_generating);
if (locationUrl == null) {
return;
}
try {
locationUrl = FileLocator.resolve(locationUrl);
locationUrl = FileLocator.toFileURL(locationUrl);
} catch (IOException e) {
return;
}
if ("file".equals(locationUrl.getProtocol())) { //$NON-NLS-1$
File templateDirectory = new File(locationUrl.getFile());
if (!templateDirectory.exists())
return;
generateFiles(templateDirectory, fProject, true, false, monitor);
} else if ("jar".equals(locationUrl.getProtocol())) { //$NON-NLS-1$
String file = locationUrl.getFile();
int exclamation = file.indexOf('!');
if (exclamation < 0)
return;
URL fileUrl = null;
try {
fileUrl = new URL(file.substring(0, exclamation));
} catch (MalformedURLException mue) {
return;
}
File pluginJar = new File(fileUrl.getFile());
if (!pluginJar.exists())
return;
String templateDirectory = file.substring(exclamation + 1); // "/some/path/"
IPath path = new Path(templateDirectory);
try (ZipFile zipFile = new ZipFile(pluginJar)) {
generateFiles(zipFile, path, fProject, true, false, monitor);
} catch (IOException ioe) {
}
}
monitor.subTask(""); //$NON-NLS-1$
monitor.worked(1);
}
private void generateFiles(File src, IContainer dst, boolean firstLevel, boolean binary, IProgressMonitor monitor) throws CoreException {
File[] members = src.listFiles();
for (File member : members) {
if (member.isDirectory()) {
IContainer dstContainer = null;
if (firstLevel) {
binary = false;
if (!isOkToCreateFolder(member))
continue;
if (member.getName().equals("java")) { //$NON-NLS-1$
IFolder sourceFolder = getSourceFolder(monitor);
dstContainer = generateJavaSourceFolder(sourceFolder, monitor);
} else if (member.getName().equals("bin")) { //$NON-NLS-1$
binary = true;
dstContainer = dst;
}
}
if (dstContainer == null) {
if (isOkToCreateFolder(member) == false)
continue;
String folderName = getProcessedString(member.getName(), member.getName());
dstContainer = dst.getFolder(new Path(folderName));
}
if (dstContainer instanceof IFolder && !dstContainer.exists())
((IFolder) dstContainer).create(true, true, monitor);
generateFiles(member, dstContainer, false, binary, monitor);
} else {
if (isOkToCreateFile(member)) {
if (firstLevel)
binary = false;
try (InputStream in = new FileInputStream(member);) {
copyFile(member.getName(), in, dst, binary, monitor);
} catch (IOException ioe) {
}
}
}
}
}
private void copyFile(String fileName, InputStream input, IContainer dst, boolean binary, IProgressMonitor monitor) throws CoreException {
String targetFileName = getProcessedString(fileName, fileName);
monitor.subTask(targetFileName);
IFile dstFile = dst.getFile(new Path(targetFileName));
try (InputStream stream = getProcessedStream(fileName, input, binary)) {
if (dstFile.exists()) {
dstFile.setContents(stream, true, true, monitor);
} else {
dstFile.create(stream, true, monitor);
}
} catch (IOException e) {
}
}
private void generateFiles(ZipFile zipFile, IPath path, IContainer dst, boolean firstLevel, boolean binary, IProgressMonitor monitor) throws CoreException {
int pathLength = path.segmentCount();
// Immidiate children
Map<String, ZipEntry> childZipEntries = new HashMap<>(); // "dir/" or "dir/file.java"
for (Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); zipEntries.hasMoreElements();) {
ZipEntry zipEntry = zipEntries.nextElement();
IPath entryPath = new Path(zipEntry.getName());
if (entryPath.segmentCount() <= pathLength) {
// ancestor or current directory
continue;
}
if (!path.isPrefixOf(entryPath)) {
// not a descendant
continue;
}
if (entryPath.segmentCount() == pathLength + 1) {
childZipEntries.put(zipEntry.getName(), zipEntry);
} else {
String name = entryPath.uptoSegment(pathLength + 1).addTrailingSeparator().toString();
if (!childZipEntries.containsKey(name)) {
ZipEntry dirEntry = new ZipEntry(name);
childZipEntries.put(name, dirEntry);
}
}
}
for (ZipEntry zipEnry : childZipEntries.values()) {
String name = new Path(zipEnry.getName()).lastSegment().toString();
if (zipEnry.isDirectory()) {
IContainer dstContainer = null;
if (firstLevel) {
binary = false;
if (name.equals("java")) { //$NON-NLS-1$
IFolder sourceFolder = getSourceFolder(monitor);
dstContainer = generateJavaSourceFolder(sourceFolder, monitor);
} else if (name.equals("bin")) { //$NON-NLS-1$
binary = true;
dstContainer = dst;
}
}
if (dstContainer == null) {
if (isOkToCreateFolder(new File(path.toFile(), name)) == false)
continue;
String folderName = getProcessedString(name, name);
dstContainer = dst.getFolder(new Path(folderName));
}
if (dstContainer instanceof IFolder && !dstContainer.exists())
((IFolder) dstContainer).create(true, true, monitor);
generateFiles(zipFile, path.append(name), dstContainer, false, binary, monitor);
} else {
if (isOkToCreateFile(new File(path.toFile(), name))) {
if (firstLevel)
binary = false;
try (InputStream in = zipFile.getInputStream(zipEnry)) {
copyFile(name, in, dst, binary, monitor);
} catch (IOException ioe) {
}
}
}
}
}
/**
* Tests if the folder found in the template location should be created in
* the target project. Subclasses may use this method to conditionally block
* the creation of entire directories (subject to user choices).
*
* @param sourceFolder
* the folder that is tested
* @return <code>true</code> if the provided folder should be created in
* the workspace, <code>false</code> if the values of the
* substitution variables indicate otherwise.
*/
protected boolean isOkToCreateFolder(File sourceFolder) {
boolean extensibleTemplateSelected = UpdateSplashHandlerAction.isExtensibleTemplateSelected(fTemplate);
String sourceFolderString = sourceFolder.toString();
if ((extensibleTemplateSelected == false) && sourceFolderString.endsWith("icons")) { //$NON-NLS-1$
return false;
} else if ((extensibleTemplateSelected == false) && sourceFolderString.endsWith("schema")) { //$NON-NLS-1$
return false;
}
return true;
}
/**
* Tests if the file found in the template location should be created in the
* target project. Subclasses may use this method to conditionally block
* creation of the file (subject to user choices).
*
* @param sourceFile
* the file found in the template location that needs to be
* created.
* @return <samp>true </samp> if the specified file should be created in the
* project or <samp>false </samp> to skip it. The default
* implementation is <samp>true </samp>.
*/
protected boolean isOkToCreateFile(File sourceFile) {
String javaSuffix = ".java"; //$NON-NLS-1$
String targetFile = fClass + javaSuffix;
String copyFile = sourceFile.toString();
// Prevent needless copying
// TODO: MP: SPLASH: Propagate / share copy prevention code with org.eclipse.pde.internal.ui.templates.ide.SplashHandlersTemplate
if (copyFile.endsWith(javaSuffix)) {
if ((copyFile.endsWith(targetFile) == false) || fProject.exists(new Path("src" + '/' + fPackage.replace('.', '/') + '/' + targetFile))) { //$NON-NLS-1$
return false;
}
} else if (copyFile.endsWith("splash.bmp") && //$NON-NLS-1$
(fProject.exists(new Path("splash.bmp")))) { //$NON-NLS-1$
return false;
} else if (copyFile.endsWith(".png")) { //$NON-NLS-1$
if (copyFile.endsWith("af.png") && //$NON-NLS-1$
fProject.exists(new Path("icons/af.png"))) { //$NON-NLS-1$
return false;
} else if (copyFile.endsWith("embedded.png") && //$NON-NLS-1$
fProject.exists(new Path("icons/embedded.png"))) { //$NON-NLS-1$
return false;
} else if (copyFile.endsWith("enterprise.png") && //$NON-NLS-1$
fProject.exists(new Path("icons/enterprise.png"))) { //$NON-NLS-1$
return false;
} else if (copyFile.endsWith("rcp.png") && //$NON-NLS-1$
fProject.exists(new Path("icons/rcp.png"))) { //$NON-NLS-1$
return false;
} else if (copyFile.endsWith("languages.png") && //$NON-NLS-1$
fProject.exists(new Path("icons/languages.png"))) { //$NON-NLS-1$
return false;
}
} else if (copyFile.endsWith("splashExtension.exsd") && //$NON-NLS-1$
(fProject.exists(new Path("schema/splashExtension.exsd")))) { //$NON-NLS-1$
return false;
}
return true;
}
/**
* Returns the folder with Java files in the target project. The default
* implementation looks for source folders in the classpath of the target
* folders and picks the first one encountered. Subclasses may override this
* behavior.
*
* @param monitor
* progress monitor to use
* @return source folder that will be used to generate Java files or
* <samp>null </samp> if none found.
* @throws CoreException
*/
protected IFolder getSourceFolder(IProgressMonitor monitor) throws CoreException {
IFolder sourceFolder = null;
try {
IJavaProject javaProject = JavaCore.create(fProject);
IClasspathEntry[] classpath = javaProject.getRawClasspath();
for (IClasspathEntry entry : classpath) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
IPath path = entry.getPath().removeFirstSegments(1);
if (path.segmentCount() > 0)
sourceFolder = fProject.getFolder(path);
break;
}
}
} catch (JavaModelException e) {
}
return sourceFolder;
}
/**
* @see IVariableProvider#getValue(String)
*/
@Override
public Object getValue(String key) {
return getKeyValue(key);
}
private IFolder generateJavaSourceFolder(IFolder sourceFolder, IProgressMonitor monitor) throws CoreException {
Object packageValue = getValue(KEY_PACKAGE_NAME);
//
String packageName = packageValue != null ? packageValue.toString() : null;
if (packageName == null)
packageName = fModel.getPluginBase().getId();
IPath path = new Path(packageName.replace('.', File.separatorChar));
if (sourceFolder != null)
path = sourceFolder.getProjectRelativePath().append(path);
for (int i = 1; i <= path.segmentCount(); i++) {
IPath subpath = path.uptoSegment(i);
IFolder subfolder = fProject.getFolder(subpath);
if (subfolder.exists() == false)
subfolder.create(true, true, monitor);
}
return fProject.getFolder(path);
}
private String getProcessedString(String fileName, String source) {
if (source.indexOf('$') == -1)
return source;
int loc = -1;
StringBuilder buffer = new StringBuilder();
boolean replacementMode = false;
for (int i = 0; i < source.length(); i++) {
char c = source.charAt(i);
if (c == '$') {
if (replacementMode) {
String key = source.substring(loc, i);
String value = key.length() == 0 ? "$" //$NON-NLS-1$
: getReplacementString(fileName, key);
buffer.append(value);
replacementMode = false;
} else {
replacementMode = true;
loc = i + 1;
continue;
}
} else if (!replacementMode)
buffer.append(c);
}
return buffer.toString();
}
/**
* The default implementation of this method provides values of the
* following keys: <samp>pluginClass </samp>, <samp>pluginId </samp> and
* <samp>pluginName </samp>.
*
* @see ITemplateSection#getReplacementString(String,String)
*/
public String getReplacementString(String fileName, String key) {
String result = getKeyValue(key);
return result != null ? result : key;
}
private String getKeyValue(String key) {
if (fModel == null)
return null;
// TODO: MP: SPLASH: Broken and not needed, underlying model does not have class, id, translated name parameters defined
if (key.equals(KEY_PLUGIN_CLASS) && fModel instanceof IPluginModel) {
IPlugin plugin = (IPlugin) fModel.getPluginBase();
return plugin.getClassName();
}
// TODO: MP: SPLASH: Broken and not needed, underlying model does not have class, id, translated name parameters defined
if (key.equals(KEY_ACTIVATOR_SIMPLE) && fModel instanceof IPluginModel) {
IPlugin plugin = (IPlugin) fModel.getPluginBase();
String qualified = plugin.getClassName();
if (qualified != null) {
int lastDot = qualified.lastIndexOf('.');
return (lastDot != -1 && lastDot < qualified.length() - 1) ? qualified.substring(lastDot + 1) : qualified;
}
}
if (key.equals(KEY_PLUGIN_ID)) {
return fPluginID;
// Old implementation, does not work
//IPluginBase plugin = fModel.getPluginBase();
//return plugin.getId();
}
// TODO: MP: SPLASH: Broken and not needed, underlying model does not have class, id, translated name parameters defined
if (key.equals(KEY_PLUGIN_NAME)) {
IPluginBase plugin = fModel.getPluginBase();
return plugin.getTranslatedName();
}
if (key.equals(KEY_PACKAGE_NAME) && fModel instanceof IPluginModel) {
return fPackage;
// Old implementation, does not work
// IPlugin plugin = (IPlugin) fModel.getPluginBase();
// String qualified = plugin.getClassName();
// if (qualified != null) {
// int lastDot = qualified.lastIndexOf('.');
// return (lastDot != -1) ? qualified.substring(0, lastDot) : qualified;
// }
}
return null;
}
private InputStream getProcessedStream(String fileName, InputStream stream, boolean binary) throws IOException, CoreException {
if (binary)
return stream;
InputStreamReader reader = new InputStreamReader(stream);
int bufsize = 1024;
char[] cbuffer = new char[bufsize];
int read = 0;
StringBuilder keyBuffer = new StringBuilder();
StringBuilder outBuffer = new StringBuilder();
StringBuilder preBuffer = new StringBuilder();
boolean newLine = true;
ControlStack preStack = new ControlStack();
preStack.setValueProvider(this);
boolean replacementMode = false;
boolean preprocessorMode = false;
boolean escape = false;
while (read != -1) {
read = reader.read(cbuffer);
for (int i = 0; i < read; i++) {
char c = cbuffer[i];
if (escape) {
StringBuilder buf = preprocessorMode ? preBuffer : outBuffer;
buf.append(c);
escape = false;
continue;
}
if (newLine && c == '%') {
// preprocessor line
preprocessorMode = true;
preBuffer.delete(0, preBuffer.length());
continue;
}
if (preprocessorMode) {
if (c == '\\') {
escape = true;
continue;
}
if (c == '\n') {
// handle line
preprocessorMode = false;
newLine = true;
String line = preBuffer.toString().trim();
preStack.processLine(line);
continue;
}
preBuffer.append(c);
continue;
}
if (preStack.getCurrentState() == false) {
continue;
}
if (c == '$') {
if (replacementMode) {
replacementMode = false;
String key = keyBuffer.toString();
String value = key.length() == 0 ? "$" //$NON-NLS-1$
: getReplacementString(fileName, key);
outBuffer.append(value);
keyBuffer.delete(0, keyBuffer.length());
} else {
replacementMode = true;
}
} else {
if (replacementMode)
keyBuffer.append(c);
else {
outBuffer.append(c);
if (c == '\n') {
newLine = true;
} else
newLine = false;
}
}
}
}
return new ByteArrayInputStream(outBuffer.toString().getBytes(fProject.getDefaultCharset()));
}
private String[] getDirectoryCandidates() {
double version = getTargetVersion();
ArrayList<String> result = new ArrayList<>();
if (version >= 3.3)
result.add("templates_3.3" + "/" + getSectionId() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (version >= 3.2)
result.add("templates_3.2" + "/" + getSectionId() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (version >= 3.1)
result.add("templates_3.1" + "/" + getSectionId() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (version >= 3.0)
result.add("templates_3.0" + "/" + getSectionId() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return result.toArray(new String[result.size()]);
}
public String getSectionId() {
return ISplashHandlerConstants.F_UNQUALIFIED_EXTENSION_ID;
}
protected double getTargetVersion() {
try {
IPluginBase plugin = fModel.getPluginBase();
if (plugin instanceof IBundlePluginBase)
return Double.parseDouble(((IBundlePluginBase) plugin).getTargetVersion());
} catch (NumberFormatException e) {
}
return TargetPlatformHelper.getTargetVersion();
}
}