Eclipse Plugin Generation

Post at — Jul 26, 2017
#pde #plugin #generation #programmatically

I am currently working with eclipse projects. Lots of eclipse projects. So much that I want to generate eclipse projects using java code (I hope to write another article of the why of this soon).

Surprisingly this task is not really complicated but requires an awful lot of code to work.

So once I made it work if felt the need to share it as I did not found much documentation.

The following code is just an simplification of org.eclipse.pde.internal.ui.wizards.plugin.NewProjectCreationOperation from the Plugin Development Environment (PDE) project.

In eclipse NewProjectCreationOperation is called at the end of eclipse new plugin wizard (org.eclipse.pde.ui.templates.NewPluginProjectFromTemplateWizard) and initialize a new eclipse plugin project according to the choices done by the user during the wizard steps.

I just simplified the pde code by removing every UI related dependencies and replacing the object holding the wizard inputs by static fields directly in the class (for simplicity sake).

The minimal set of dependencies needed to make the following code work is:

  • org.eclipse.core.runtime;bundle-version=“3.13.0”
  • org.eclipse.core.resources;bundle-version=“3.12.0”
  • org.eclipse.jdt.core;bundle-version=“3.13.0”
  • org.eclipse.pde.core;bundle-version=“3.11.100”

The entry point is the execute method that will create an minimal eclipse plugin project named “myPlugin”.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
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.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.pde.core.build.IBuildEntry;
import org.eclipse.pde.core.build.IBuildModelFactory;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.IPluginLibrary;
import org.eclipse.pde.internal.core.ClasspathComputer;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.TargetPlatformHelper;
import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
import org.eclipse.pde.internal.core.bundle.BundlePluginBase;
import org.eclipse.pde.internal.core.bundle.WorkspaceBundlePluginModel;
import org.eclipse.pde.internal.core.bundle.WorkspaceBundlePluginModelBase;
import org.eclipse.pde.internal.core.ibundle.IBundle;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginBase;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
import org.eclipse.pde.internal.core.natures.PDE;
import org.eclipse.pde.internal.core.plugin.WorkspacePluginModelBase;
import org.eclipse.pde.internal.core.project.PDEProject;
import org.eclipse.pde.internal.core.util.CoreUtility;
import org.osgi.framework.Constants;

public class PluginApplicationCreator {
	private WorkspacePluginModelBase fModel;
	private final String version = "0.0.0";
	private final String sourceFolderName = "src";
	private final String outputFolderName = "bin";
	private final String confExecutionEnvironment = "JavaSE-1.8";
	private final String bundleId = "myPluginBundleId";
	private final String configTargetVersion = "3.8";
	private final String projectName = "myPlugin";

	public static void main(final String[] args) throws CoreException {
		new PluginApplicationCreator().execute();
	}

	public void execute() throws CoreException {
		final IProject project = this.createProject();

		if (project.hasNature(JavaCore.NATURE_ID)) {
			setClasspath(project);
		}

		createManifest(project);
		createBuildPropertiesFile(project);

		// not sure when this call is usefull.
		adjustManifests(null, project, fModel.getPluginBase());

		fModel.save();

	}

	private void adjustManifests(final IProgressMonitor monitor, final IProject project, final IPluginBase bundle)
			throws CoreException {
		final IPluginLibrary[] libs = fModel.getPluginBase().getLibraries();
		final Set<String> packages = new TreeSet<>();
		for (final IPluginLibrary lib : libs) {
			final String[] filters = lib.getContentFilters();
			// if a library is fully exported, then export all source packages (since we
			// don't know which source folders go with which library)
			if (filters.length == 1 && filters[0].equals("**")) {
				addAllSourcePackages(project, packages);
				break;
			}
			for (final String filter : filters) {
				if (filter.endsWith(".*"))
					packages.add(filter.substring(0, filter.length() - 2));
			}
		}
		if (!packages.isEmpty()) {
			final IBundle iBundle = ((WorkspaceBundlePluginModelBase) fModel).getBundleModel().getBundle();
			iBundle.setHeader(Constants.EXPORT_PACKAGE, getCommaValuesFromPackagesSet(packages, version));
		}
	}

	private void addAllSourcePackages(final IProject project, final Set<String> list) {
		try {
			final IJavaProject javaProject = JavaCore.create(project);
			final IClasspathEntry[] classpath = javaProject.getRawClasspath();
			for (final IClasspathEntry entry : classpath) {
				if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
					final IPath path = entry.getPath().removeFirstSegments(1);
					if (path.segmentCount() > 0) {
						final IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(project.getFolder(path));
						final IJavaElement[] children = root.getChildren();
						for (final IJavaElement element : children) {
							final IPackageFragment frag = (IPackageFragment) element;
							if (frag.getChildren().length > 0 || frag.getNonJavaResources().length > 0)
								list.add(element.getElementName());
						}
					}
				}
			}
		} catch (final JavaModelException e) {
		}
	}

	private void createBuildPropertiesFile(final IProject project) throws CoreException {
		final IFile file = PDEProject.getBuildProperties(project);
		if (!file.exists()) {
			final WorkspaceBuildModel model = new WorkspaceBuildModel(file);
			final IBuildModelFactory factory = model.getFactory();

			// BIN.INCLUDES
			final IBuildEntry binEntry = factory.createEntry(IBuildEntry.BIN_INCLUDES);
			fillBinIncludes(project, binEntry);
			model.getBuild().add(binEntry);
			model.save();
		}
	}

	private void fillBinIncludes(final IProject project, final IBuildEntry binEntry) throws CoreException {
		binEntry.addToken(ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR);
		binEntry.addToken("META-INF/");
	}

	private void setClasspath(final IProject project) throws JavaModelException, CoreException {
		final IJavaProject javaProject = JavaCore.create(project);
		// Set output folder
		if (outputFolderName != null) {
			final IPath path = project.getFullPath().append(outputFolderName);
			javaProject.setOutputLocation(path, null);
		}
		final IClasspathEntry[] entries = getClassPathEntries(javaProject);
		javaProject.setRawClasspath(entries, null);
	}

	private IProject createProject() throws CoreException {

		final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		final IProject project = root.getProject(projectName);
		project.delete(true, null);
		project.create(null);
		project.open(null);

		CoreUtility.addNatureToProject(project, PDE.PLUGIN_NATURE, null);
		CoreUtility.addNatureToProject(project, JavaCore.NATURE_ID, null);
		final IFolder folder = project.getFolder(sourceFolderName);
		if (!folder.exists())
			CoreUtility.createFolder(folder);
		return project;
	}

	private IClasspathEntry[] getClassPathEntries(final IJavaProject project) {
		final IClasspathEntry[] internalClassPathEntries = getInternalClassPathEntries(project);
		final IClasspathEntry[] entries = new IClasspathEntry[internalClassPathEntries.length + 2];
		System.arraycopy(internalClassPathEntries, 0, entries, 2, internalClassPathEntries.length);

		// Set EE of new project
		String executionEnvironment = null;
		executionEnvironment = confExecutionEnvironment;
		ClasspathComputer.setComplianceOptions(project, executionEnvironment);
		entries[0] = ClasspathComputer.createJREEntry(executionEnvironment);
		entries[1] = ClasspathComputer.createContainerEntry();

		return entries;
	}

	private IClasspathEntry[] getInternalClassPathEntries(final IJavaProject project) {
		if (sourceFolderName == null) {
			return new IClasspathEntry[0];
		}
		final IClasspathEntry[] entries = new IClasspathEntry[1];
		final IPath path = project.getProject().getFullPath().append(sourceFolderName);
		entries[0] = JavaCore.newSourceEntry(path);
		return entries;
	}

	private void createManifest(final IProject project) throws CoreException {
		final IFile fragmentXml = PDEProject.getFragmentXml(project);
		final IFile pluginXml = PDEProject.getPluginXml(project);
		final IFile manifest = PDEProject.getManifest(project);
		fModel = new WorkspaceBundlePluginModel(manifest, pluginXml);
		final IPluginBase pluginBase = fModel.getPluginBase();
		final String targetVersion = configTargetVersion;
		pluginBase.setSchemaVersion(TargetPlatformHelper.getSchemaVersionForTargetVersion(targetVersion));
		pluginBase.setId(bundleId);
		pluginBase.setVersion(version);
		pluginBase.setName(projectName);
		pluginBase.setProviderName("");
		if (fModel instanceof IBundlePluginModelBase) {
			final IBundlePluginModelBase bmodel = ((IBundlePluginModelBase) fModel);
			((IBundlePluginBase) bmodel.getPluginBase()).setTargetVersion(targetVersion);
			bmodel.getBundleModel().getBundle().setHeader(Constants.BUNDLE_MANIFESTVERSION, "2");
		}

		final IBundle bundle = ((BundlePluginBase) pluginBase).getBundle();
		final String exeEnvironment = confExecutionEnvironment;
		if (exeEnvironment != null) {
			bundle.setHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, exeEnvironment);
		}
	}

	private String getCommaValuesFromPackagesSet(final Set<String> values, final String version) {
		final StringBuffer buffer = new StringBuffer();
		final Iterator<String> iter = values.iterator();
		while (iter.hasNext()) {
			if (buffer.length() > 0) {
				buffer.append(",\n "); // space required for multiline headers
			}
			final String value = iter.next().toString();
			buffer.append(value);

			if (value.indexOf(";version=") == -1 && (version != null) && (values.size() == 1)) {
				buffer.append(";version=\"").append(version).append("\""); //$NON-NLS-2$
			}
		}
		return buffer.toString();
	}

}

It feels like this code could be heavily simplified, but it does the job.