package org.bndtools.core.templates.enroute;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bndtools.templating.Resource;
import org.bndtools.templating.ResourceMap;
import org.bndtools.templating.StringResource;
import org.bndtools.templating.Template;
import org.bndtools.templating.util.AttributeDefinitionImpl;
import org.bndtools.templating.util.ObjectClassDefinitionImpl;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.spi.RegistryContributor;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
import org.osgi.service.metatype.AttributeDefinition;
import org.osgi.service.metatype.ObjectClassDefinition;
import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.clauses.ExportedPackage;
import aQute.bnd.build.model.clauses.VersionedClause;
import aQute.bnd.header.Attrs;
import aQute.bnd.properties.Document;
import bndtools.Plugin;
public class EnrouteProjectTemplate implements Template, IExecutableExtension {
private static final String PROP_PROJECT_NAME = "projectName";
private static final String PROP_SRC_DIR = "srcDir";
private static final String PROP_TEST_SRC_DIR = "testSrcDir";
private static final String PROP_BASE_PACKAGE_NAME = "basePackageName";
private static final String PROP_BASE_PACKAGE_DIR = "basePackageDir";
static Pattern LAST_PART = Pattern.compile(".*\\.([^.]+)");
static Pattern SKIP = Pattern.compile("\\.classpath|\\.project");
static Pattern TOP_LEVEL = Pattern.compile("^(?:(?:com|biz|org|net|uk.co|gnu|gov|mil|[a-z][a-z]|info|name)\\.)(.*)", Pattern.CASE_INSENSITIVE);
private static final Map<String,String> parameters = new HashMap<>();
static {
parameters.put(PROP_SRC_DIR, "Source Directory");
parameters.put(PROP_TEST_SRC_DIR, "Test Source Directory");
parameters.put(PROP_BASE_PACKAGE_NAME, "Base Java Package");
parameters.put(PROP_BASE_PACKAGE_DIR, "Base Java Package Directory");
}
private String name;
private String category;
private String description;
private int priority = 0;
private Bundle bundle;
private URI iconUri = null;
private URI docUri = null;
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
name = config.getAttribute("name");
category = config.getAttribute("category");
if (category == null)
category = "Uncategorised";
description = String.format("from %s (installed plug-in)", config.getContributor().getName());
RegistryContributor contributor = (RegistryContributor) config.getContributor();
long bundleId = Long.parseLong(contributor.getActualId());
bundle = Plugin.getDefault().getBundleContext().getBundle(bundleId);
try {
iconUri = pathToURI(bundle, config.getAttribute("icon"));
docUri = pathToURI(bundle, config.getAttribute("doc"));
} catch (URISyntaxException e) {
throw new CoreException(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Error processing extension attributes.", e));
}
String priorityStr = config.getAttribute("priority");
if (priorityStr != null)
priority = Integer.parseInt(priorityStr);
}
private URI pathToURI(Bundle b, String path) throws URISyntaxException {
if (path == null)
return null;
URL url = b.getEntry(path);
if (url == null)
return null;
return url.toURI();
}
@Override
public String getName() {
return name;
}
@Override
public String getShortDescription() {
return description != null ? description : name;
}
@Override
public String getCategory() {
return category;
}
@Override
public URI getIcon() {
return iconUri;
}
@Override
public URI getHelpContent() {
return docUri;
}
@Override
public int getRanking() {
return priority;
}
@Override
public Version getVersion() {
return bundle.getVersion();
}
@Override
public ObjectClassDefinition getMetadata() throws Exception {
return getMetadata(new NullProgressMonitor());
}
@Override
public ObjectClassDefinition getMetadata(IProgressMonitor monitor) throws Exception {
ObjectClassDefinitionImpl ocd = new ObjectClassDefinitionImpl(name, description, iconUri);
for (Entry<String,String> entry : parameters.entrySet())
ocd.addAttribute(new AttributeDefinitionImpl(entry.getKey(), entry.getValue(), 0, AttributeDefinition.STRING), true);
return ocd;
}
private static String param(String name, Map<String,List<Object>> params) {
List<Object> list = params.get(name);
if (list == null || list.isEmpty())
throw new IllegalArgumentException("Missing required parameter: " + name);
Object object = list.get(0);
if (object instanceof String)
return (String) object;
throw new IllegalArgumentException(String.format("Unexpected type for parameter '%s': expected String, found %s", name, object != null ? object.getClass().getName() : "<null>"));
}
@Override
public ResourceMap generateOutputs(Map<String,List<Object>> parameters) throws Exception {
return generateOutputs(parameters, new NullProgressMonitor());
}
@Override
public ResourceMap generateOutputs(Map<String,List<Object>> params, IProgressMonitor monitor) throws Exception {
String projectName = param(PROP_PROJECT_NAME, params);
String pkg = param(PROP_BASE_PACKAGE_NAME, params);
String srcDir = param(PROP_SRC_DIR, params);
String testSrcDir = param(PROP_TEST_SRC_DIR, params);
ResourceMap resources = new ResourceMap();
resources.put("bnd.bnd", generateBndFile(projectName, pkg));
generateSources(resources, projectName, pkg, srcDir, testSrcDir);
return resources;
}
private Resource generateBndFile(String projectName, String pkg) {
BndEditModel model = new BndEditModel();
model.setPrivatePackages(Arrays.asList(new String[] {
pkg + ".provider"
}));
model.setExportedPackages(Arrays.asList(new ExportedPackage(projectName + ".api", new Attrs())));
model.setBundleDescription("${warning:please explain what this bundle does}");
model.setBundleVersion("1.0.0.${tstamp}");
List<VersionedClause> buildPath = new ArrayList<VersionedClause>();
List<VersionedClause> tmp;
tmp = model.getBuildPath();
if (tmp != null)
buildPath.addAll(tmp);
Attrs attrs = new Attrs();
attrs.put("version", "@1.0");
buildPath.add(new VersionedClause("osgi.enroute.base.api", attrs));
buildPath.add(new VersionedClause("osgi.enroute.base.junit", attrs));
model.setBuildPath(buildPath);
Document doc = new Document("");
model.saveChangesTo(doc);
StringResource bndBndResource = new StringResource(doc.get());
return bndBndResource;
}
static Pattern API = Pattern.compile("(.*\\.([^.]+))\\.api");
static Pattern PROVIDER = Pattern.compile("(.*\\.([^.]+))\\.(provider|adapter)");
static Pattern TEST = Pattern.compile("(.*\\.([^.]+))\\.test");
static Pattern APPLICATION = Pattern.compile("(.*\\.([^.]+))\\.(app|webapp|application)");
static Pattern WEBRESOURCE = Pattern.compile("(.*\\.([^.]+))\\.(resource|webresource)");
static Pattern EXAMPLE = Pattern.compile("(.*\\.([^.]+))\\.example");
static Pattern TUTORIAL_BASE = Pattern.compile("tutorial.base");
static Pattern UNKNOWN = Pattern.compile(".*\\.([^.]+)");
private void generateSources(ResourceMap resourceMap, String projectName, String basePackageName, String srcDir, String testSrcDir) {
String stem;
String type;
String pid;
Matcher m = API.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
type = "_api_";
} else {
m = TUTORIAL_BASE.matcher(projectName);
if (m.matches()) {
pid = "tutorial.base";
stem = "Speaker";
type = "_tutorialbase_";
} else {
m = PROVIDER.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
type = "_provider_";
} else {
m = TEST.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
type = "_test_";
} else {
m = APPLICATION.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
type = "_application_";
} else {
m = WEBRESOURCE.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
type = "_webresource_";
} else {
m = EXAMPLE.matcher(projectName);
if (m.matches()) {
pid = m.group(1);
stem = m.group(2);
basePackageName = stem;
type = "_example_";
} else {
m = UNKNOWN.matcher(projectName);
if (m.matches()) {
stem = m.group(1);
pid = projectName;
type = "_example_";
} else {
stem = projectName;
pid = projectName;
type = "_example_";
}
}
}
}
}
}
}
}
stem = Character.toUpperCase(stem.charAt(0)) + stem.substring(1);
String pkgPath = toBinaryPackage(basePackageName);
Map<String,String> regex = new LinkedHashMap<String,String>();
regex.put("_stem_", stem);
regex.put("_STEM_", stem.toUpperCase());
regex.put("_type_", type);
regex.put("_package_", pkgPath);
regex.put("_packagepath_", pkgPath);
regex.put("_example_", projectName);
regex.put("_api_", projectName);
regex.put("_provider_", projectName);
regex.put("_tutorialbase_", projectName);
regex.put("_test_", projectName);
regex.put("_application_", projectName);
regex.put("_webresource_", projectName);
regex.put("_unknown_", projectName);
regex.put("_cmd_", toCmd(stem));
regex.put("_project_", projectName);
regex.put("_project(\\.[a-zA-Z]+)_", projectName);
regex.put("_PROJECT_", toPROJECT(projectName));
regex.put("_pid_", pid);
regex.put("_src_", srcDir);
regex.put("_test_", testSrcDir);
copy(resourceMap, "/enroute/osgi.enroute.template/" + type, regex);
}
private String toBinaryPackage(String pkg) {
StringBuilder sb = new StringBuilder(pkg.toLowerCase());
boolean first = true;
for (int i = 0; i < sb.length(); i++) {
char ch = sb.charAt(i);
if (ch == '.' || ch == '/') {
sb.replace(i, i + 1, "/");
continue;
}
if (!Character.isJavaIdentifierPart(ch)) {
sb.delete(i, i + i);
i--;
continue;
}
if (first) {
first = false;
if (!Character.isJavaIdentifierStart(ch)) {
sb.delete(i, i + i);
i--;
continue;
}
}
if (ch == '-') {
sb.replace(i, i + 1, "_");
continue;
}
}
return sb.toString();
}
private String toPROJECT(String projectName) {
String name = TOP_LEVEL.matcher(projectName).replaceFirst("");
return name.toUpperCase().replace('.', ' ');
}
private String toCmd(String stem) {
StringBuilder sb = new StringBuilder(stem.toLowerCase());
outer: while (sb.length() > 7) {
for (int i = sb.length() - 1; i >= 1; i--) {
char c = sb.charAt(i);
if ("aeiouy".indexOf(c) >= 0) {
sb.delete(i, i + 1);
continue outer;
}
}
sb.delete(7, sb.length());
break;
}
return sb.toString();
}
protected void copy(ResourceMap resourceMap, String prefix, Map<String,String> regex) {
List<String> paths = new ArrayList<String>();
Map<String,URL> resources = new HashMap<String,URL>();
getPaths(bundle, prefix, paths);
for (String path : paths) {
URL resource = bundle.getResource(path);
String out = path.substring(prefix.length());
if (out.equals("src"))
out = regex.get("_src_");
else if (out.equals("test"))
out = regex.get("_test_");
else if (out.equals("bin") || out.equals("bin_test") || out.equals("generated"))
continue;
//
// Paths are replaced where a package a path, using a slash
//
out = replace(out, regex);
resources.put(out, resource);
}
//
// Content should however, use the dotted variation of package
//
String replace = regex.get("_package_").replace('/', '.');
regex.put("_package_", replace);
for (Entry<String,URL> e : resources.entrySet()) {
RegexReplacingResource resource = new RegexReplacingResource(e.getValue(), regex, "UTF-8");
resourceMap.put(e.getKey(), resource);
}
}
private void getPaths(Bundle bundle, String prefix, List<String> set) {
Enumeration<String> paths = bundle.getEntryPaths(prefix);
while (paths.hasMoreElements()) {
String path = paths.nextElement();
if (path.endsWith("/"))
getPaths(bundle, path, set);
else
set.add(path);
}
}
protected String replace(String out, Map<String,String> regex) {
String out_ = out;
for (Entry<String,String> e : regex.entrySet()) {
out_ = out_.replace(e.getKey(), e.getValue());
}
return out_;
}
@Override
public void close() throws IOException {
// nothing to do
}
}