blob: 585a029aab7523bf1848d8eb7404af57580f123b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.cauldron.bld.config;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;
import org.cauldron.bld.bnd.BundleBuilder;
import org.cauldron.bld.core.internal.model.osgi.BundleModelElement;
import org.cauldron.bld.core.internal.model.osgi.PackageExport;
import org.cauldron.bld.core.internal.model.osgi.PackageImport;
import org.cauldron.bld.core.internal.model.osgi.RequiredBundle;
import org.cauldron.sigil.model.IModelElement;
import org.cauldron.sigil.model.common.VersionRange;
import org.cauldron.sigil.model.eclipse.ISigilBundle;
import org.cauldron.sigil.model.osgi.IBundleModelElement;
import org.cauldron.sigil.model.osgi.IPackageExport;
import org.cauldron.sigil.model.osgi.IPackageImport;
import org.cauldron.sigil.model.osgi.IRequiredBundle;
import org.cauldron.sigil.model.osgi.IPackageImport.OSGiImport;
import org.osgi.framework.Version;
public class BldProject implements IBldProject, IRepositoryConfig {
private static final String OVERRIDE_PREFIX = "sigil.";
private static final int MAX_HEADER = 10240;
// cache to avoid loading the same default config for each project
private static Map<URL, BldConfig> defaultsCache = new HashMap<URL, BldConfig>();
private static Properties overrides;
private List<String> sourcePkgs;
private BldConfig config;
private BldConverter convert;
private BundleModelElement requirements;
private File baseDir;
private URI loc;
private Properties packageDefaults;
private TreeSet<String> packageWildDefaults;
private long lastModified;
/* package */BldProject(URI relLoc) {
config = new BldConfig();
convert = new BldConverter(config);
loc = new File(".").toURI().resolve(relLoc).normalize();
File f = new File(loc);
lastModified = f.lastModified();
baseDir = f.getParentFile();
}
/* package */void load() throws IOException {
// allow System property overrides, e.g.
// ANT_OPTS='-Dsigil.option\;addMissingImports=false' ant
config.merge(getOverrides());
InputStream in = null;
try {
in = loc.toURL().openStream();
BufferedInputStream bis = new BufferedInputStream(in);
bis.mark(MAX_HEADER);
readHeader(bis);
bis.reset();
Properties p = new Properties();
p.load(bis);
config.merge(p);
Properties unknown = config.getUnknown();
if (!unknown.isEmpty())
System.err.println("WARN: unknown keys " + unknown.keySet() + " in " + loc);
loadDefaults(p);
requirements = parseRequirements();
}
finally {
if ( in != null ) {
in.close();
}
}
}
/* package */void loadDefaults(Properties p) throws IOException {
BldConfig c = loadDefaults(p, baseDir, null);
config.setDefault(c);
Properties options = config.getProps(null, BldConfig.P_OPTION);
if (!options.containsKey(BldAttr.OPTION_ADD_IMPORTS))
c.setProp(null, BldConfig.P_OPTION, BldAttr.OPTION_ADD_IMPORTS, "true");
// default omitUnusedImports option depends on number of bundles...
// we set it here to avoid it being written by save(),
// but as this may alter cached defaults, once set we have to reset it
// for each project.
boolean omitSet = options.containsKey("__omit_set__");
boolean multiple = getBundleIds().size() > 1;
if (multiple || omitSet) {
if (!options.containsKey(BldAttr.OPTION_OMIT_IMPORTS) || omitSet) {
c.setProp(null, BldConfig.P_OPTION, BldAttr.OPTION_OMIT_IMPORTS, multiple + "");
c.setProp(null, BldConfig.P_OPTION, "__omit_set__", "true");
}
}
}
private synchronized BldConfig loadDefaults(Properties props, File base, BldConfig dflt)
throws IOException {
boolean cached = false;
String defaults = props.getProperty(BldConfig.S_DEFAULTS, "-"
+ IBldProject.PROJECT_DEFAULTS);
if (base != null && defaults.length() > 0) {
boolean ignore = defaults.startsWith("-");
if (ignore)
defaults = defaults.substring(1);
try {
File file = new File(base, defaults).getCanonicalFile();
URL url = file.toURL();
if (dflt == null) {
dflt = defaultsCache.get(url);
if (dflt != null)
return dflt;
dflt = new BldConfig();
defaultsCache.put(url, dflt);
cached = true;
}
Properties p = new Properties();
p.load(url.openStream());
dflt.merge(p);
ignore = false;
loadDefaults(p, file.getParentFile(), dflt);
} catch (IOException e) {
if (!ignore)
throw e;
}
}
if (dflt == null)
return new BldConfig();
if (cached) {
Properties unknown = dflt.getUnknown();
if (!unknown.isEmpty())
System.err.println("WARN: unknown keys " + unknown.keySet() + " in defaults for "
+ loc);
}
return dflt;
}
private static Properties getOverrides() {
if (overrides == null) {
overrides = new Properties();
Properties sysProps = System.getProperties();
for (Object okey : sysProps.keySet()) {
String key = (String) okey;
if (key.startsWith(OVERRIDE_PREFIX)) {
overrides.setProperty(key.substring(OVERRIDE_PREFIX.length()), sysProps
.getProperty(key));
}
}
}
return overrides;
}
private void readHeader(InputStream in) throws IOException {
BufferedReader r = new BufferedReader(new InputStreamReader(in));
StringBuffer header = new StringBuffer();
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("#")) {
header.append(line);
header.append("\n");
} else {
config.setComment(header.toString());
break;
}
}
}
public File resolve(String path) {
File file = new File(path);
if (!file.isAbsolute()) {
// can't use loc.resolve(value), as value may not be valid URI.
file = new File(baseDir, path);
}
return file;
}
public String getVersion() {
String version = config.getString(null, BldConfig.S_VERSION);
return version == null ? "0" : version;
}
public IBundleModelElement getDependencies() {
IBundleModelElement dependencies = new BundleModelElement();
for (IModelElement element : getRequirements().children()) {
if (element instanceof IPackageImport) {
IPackageImport import1 = (IPackageImport) element;
if (!import1.isDependency())
continue;
IPackageImport pi = (IPackageImport) (element.clone());
pi.setParent(null);
dependencies.addImport(pi);
} else {
IRequiredBundle rb = (IRequiredBundle) (element.clone());
rb.setParent(null);
dependencies.addRequiredBundle(rb);
}
}
boolean containsComposite = false;
for (IBldBundle bundle : getBundles()) {
if (!bundle.getComposites().isEmpty()) {
containsComposite = true;
break;
}
}
// add dependency on component activator
if (containsComposite) {
PackageImport pi = new PackageImport();
pi.setPackageName(BundleBuilder.COMPONENT_ACTIVATOR_PKG);
pi.setOSGiImport(OSGiImport.NEVER);
dependencies.addImport(pi);
}
return dependencies;
}
private IBundleModelElement getRequirements() {
return requirements;
}
/*
* private boolean globMatch(String pkg, Set<String> set) { // exact match
* if (set.contains(pkg)) return true;
*
* // org.foo.bar matches org.foo. for (String glob : set) { if
* (glob.matches(pkg)) { return true; } }
*
* return false; }
*/
/**
* set internal OSGiImport and isDependency flags, based on external
* resolution= attribute.
*
* OSGiImport: AUTO ALWAYS NEVER dependency: default - compile !dependency:
* auto runtime ignore
*
*/
private void setResolve(IPackageImport pi, String resolve) throws IOException {
if (pi.isOptional())
pi.setDependency(false);
if (BldAttr.RESOLVE_COMPILE.equals(resolve)) {
if (pi.isOptional())
pi.setDependency(true);
else
pi.setOSGiImport(OSGiImport.NEVER);
} else if (BldAttr.RESOLVE_RUNTIME.equals(resolve)) {
pi.setDependency(false);
pi.setOSGiImport(OSGiImport.ALWAYS);
} else if (BldAttr.RESOLVE_AUTO.equals(resolve)) {
pi.setDependency(false);
} else if (BldAttr.RESOLVE_IGNORE.equals(resolve)) {
pi.setDependency(false);
pi.setOSGiImport(OSGiImport.NEVER);
} else if (resolve != null) {
throw new IOException("Bad attribute value: " + BldAttr.RESOLVE_ATTRIBUTE + "="
+ resolve);
}
}
/**
* get external resolve= attribute from internal PackageImport flags. This
* is called from BldConverter.setBundle().
*/
public static String getResolve(IPackageImport pi, boolean isDependency) {
OSGiImport osgiImport = pi.getOSGiImport();
String resolve = null;
if (isDependency) {
if (osgiImport.equals(OSGiImport.NEVER) || pi.isOptional())
resolve = BldAttr.RESOLVE_COMPILE;
} else {
switch (osgiImport) {
case ALWAYS:
resolve = BldAttr.RESOLVE_RUNTIME;
break;
case AUTO:
resolve = BldAttr.RESOLVE_AUTO;
break;
case NEVER:
resolve = BldAttr.RESOLVE_IGNORE;
break;
}
}
return resolve;
}
public String getDefaultPackageVersion(String name) {
if (packageDefaults == null) {
packageDefaults = config.getProps(null, BldConfig.P_PACKAGE_VERSION);
packageWildDefaults = new TreeSet<String>();
for (Object key : packageDefaults.keySet()) {
String pkg = (String) key;
if (pkg.endsWith("*")) {
packageWildDefaults.add(pkg.substring(0, pkg.length() - 1));
}
}
}
String version = packageDefaults.getProperty(name);
if (version == null) {
for (String pkg : packageWildDefaults) {
if (name.startsWith(pkg)) {
version = packageDefaults.getProperty(pkg + "*");
// break; -- don't break, as we want the longest match
}
}
}
return version;
}
private synchronized BundleModelElement parseRequirements() throws IOException {
BundleModelElement reqs = new BundleModelElement();
List<String> sourceContents = getSourcePkgs();
HashSet<String> exports = new HashSet<String>();
for (IBldBundle bundle : getBundles()) {
for (IPackageExport export : bundle.getExports()) {
exports.add(export.getPackageName());
}
}
Map<String, Map<String, String>> imports = config.getMap(null, BldConfig.M_IMPORTS);
for (String name : imports.keySet()) {
Map<String, String> attr = imports.get(name);
String resolve = attr.get(BldAttr.RESOLVE_ATTRIBUTE);
String resolution = attr.get(BldAttr.RESOLUTION_ATTRIBUTE);
String versions = attr.containsKey(BldAttr.VERSION_ATTRIBUTE) ? attr
.get(BldAttr.VERSION_ATTRIBUTE) : getDefaultPackageVersion(name);
PackageImport pi = new PackageImport();
pi.setPackageName(name);
// avoid dependency on self-exports
// XXX: BldConverter.setBundle contains similar logic
if (exports.contains(name)
&& (sourceContents.contains(name) || sourceContents.isEmpty())) {
pi.setDependency(false);
if (versions == null)
versions = getVersion();
}
if (!checkVersionRange(versions)) {
throw new IOException("Failed to parse version range for " + resolve
+ " missing \"'s around version range?");
}
pi.setVersions(VersionRange.parseVersionRange(versions));
if (BldAttr.RESOLUTION_OPTIONAL.equals(resolution)) {
pi.setOptional(true);
} else if (resolution != null) {
throw new IOException("Bad attribute value: " + BldAttr.RESOLUTION_ATTRIBUTE + "="
+ resolution);
}
setResolve(pi, resolve);
reqs.addImport(pi);
}
Map<String, Map<String, String>> requires = config.getMap(null, BldConfig.M_REQUIRES);
Properties bundleDefaults = config.getProps(null, BldConfig.P_BUNDLE_VERSION);
if (requires != null) {
for (String name : requires.keySet()) {
Map<String, String> attr = requires.get(name);
String versions = attr.containsKey(BldAttr.VERSION_ATTRIBUTE) ? attr
.get(BldAttr.VERSION_ATTRIBUTE) : bundleDefaults.getProperty(name);
RequiredBundle rb = new RequiredBundle();
rb.setSymbolicName(name);
rb.setVersions(VersionRange.parseVersionRange(versions));
reqs.addRequiredBundle(rb);
}
}
for (IBldBundle bundle : getBundles()) {
IRequiredBundle fh = bundle.getFragmentHost();
if (fh != null)
reqs.addRequiredBundle(fh);
}
return reqs;
}
private boolean checkVersionRange(String versions) {
if (versions == null || versions.length() == 0) {
return true;
} else {
switch (versions.charAt(0)) {
case '(':
case '[':
switch (versions.charAt(versions.length() - 1)) {
case ')':
case ']':
return true;
default:
return false;
}
default:
return true;
}
}
}
public List<String> getBundleIds() {
List<String> ids = config.getList(null, BldConfig.C_BUNDLES);
if (ids == null)
return Collections.emptyList();
return ids;
}
public List<IBldBundle> getBundles() {
ArrayList<IBldBundle> list = new ArrayList<IBldBundle>();
for (String id : getBundleIds()) {
list.add(new BldBundle(id));
}
return list;
}
// Implement IBldConfig: getRepositoryConfig
public Map<String, Properties> getRepositoryConfig() {
HashMap<String, Properties> map = new HashMap<String, Properties>();
final Map<String, String> env = System.getenv();
final Properties props = new Properties();
try {
// supports ${.} and ${..} expansions
props.setProperty(".", resolve(".").getCanonicalPath());
props.setProperty("..", resolve("..").getCanonicalPath());
} catch (IOException e) {
}
for (String name : config.getList(null, BldConfig.C_REPOSITORIES)) {
Properties repo = config.getProps(null, name);
for (Object k : repo.keySet()) {
String key = (String) k;
String value = repo.getProperty(key);
String expand = BldUtil.expand(value, new Properties() {
public String getProperty(String name) {
return props.getProperty(name, env.get(name));
}
});
if (!value.equals(expand)) {
value = expand;
repo.setProperty(key, value);
}
// backwards compatible support before ${.} and ${..} was added
if (value.startsWith("./") || value.startsWith("../")) {
try {
// need canonical path, to normalise
value = resolve(value).getCanonicalPath();
} catch (IOException e) {
}
repo.setProperty(key, value);
}
}
map.put(name, repo);
}
return map;
}
public Properties getOptions() {
return config.getProps(null, BldConfig.P_OPTION);
}
public Properties getDefaultPackageVersions() {
return config.getProps(null, BldConfig.P_PACKAGE_VERSION);
}
public ISigilBundle getDefaultBundle() {
List<String> bundles = getBundleIds();
if (bundles.isEmpty())
return null;
String id = bundles.get(0);
return getSigilBundle(id);
}
public ISigilBundle getSigilBundle(String id) {
BldBundle bundle = new BldBundle(id);
return convert.getBundle(id, bundle);
}
public void setDefaultBundle(ISigilBundle bundle) {
setSigilBundle(null, bundle);
}
public void setSigilBundle(String id, ISigilBundle bundle) {
List<String> ids = getBundleIds();
if (ids.isEmpty()) {
ArrayList<String> list = new ArrayList<String>();
list.add(id == null ? bundle.getBundleInfo().getSymbolicName() : id);
config.setList(null, BldConfig.C_BUNDLES, list);
} else if (id == null) {
id = ids.get(0);
} else if (!ids.contains(id)) {
List<String> list = config.getList(null, BldConfig.C_BUNDLES);
list.add(id);
config.setList(null, BldConfig.C_BUNDLES, list);
}
if (ids.size() == 1)
id = null; // don't prefix default bundle with id
convert.setBundle(id, bundle);
}
public void save() throws IOException {
saveAs(new File(loc));
}
public void saveAs(File path) throws IOException {
File part = new File(path.getPath() + ".part");
saveTo(new FileOutputStream((part)));
path.delete();
if (!part.renameTo(path))
throw new IOException("failed to rename " + part + " to " + path);
}
public void saveTo(OutputStream out) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out));
config.write(writer);
writer.close();
}
public List<String> getSourceDirs() {
List<String> list = config.getList(null, BldConfig.L_SRC_CONTENTS);
return list != null ? list : Collections.<String> emptyList();
}
public List<String> getSourcePkgs() {
if (sourcePkgs == null) {
sourcePkgs = new ArrayList<String>();
for (String src : getSourceDirs()) {
File dir = resolve(src);
if (!dir.isDirectory()) {
System.err.println("WARN: sourcedir does not exist: " + dir);
continue;
// throw new RuntimeException("sourcedir: " + dir +
// " : is not a directory.");
}
findSrcPkgs(dir, null, sourcePkgs);
}
}
return sourcePkgs;
}
private void findSrcPkgs(File dir, String pkg, List<String> result) {
ArrayList<File> dirs = new ArrayList<File>();
boolean found = false;
for (String name : dir.list()) {
if (name.endsWith(".java")) {
found = true;
} else if (!name.equals(".svn")) {
File d = new File(dir, name);
if (d.isDirectory())
dirs.add(d);
}
}
if (pkg == null) {
pkg = "";
} else if (pkg.equals("")) {
pkg = dir.getName();
} else {
pkg = pkg + "." + dir.getName();
}
if (found)
result.add(pkg);
for (File d : dirs)
findSrcPkgs(d, pkg, result);
}
/**
* BldBundle
*
*/
class BldBundle implements IBldBundle {
private String id;
public BldBundle(String id) {
this.id = id;
}
public File resolve(String path) {
return BldProject.this.resolve(path);
}
private String getString(String key) {
return config.getString(id, key);
}
private List<String> getList(String key) {
List<String> list = config.getList(id, key);
return list != null ? list : Collections.<String> emptyList();
}
private Map<String, Map<String, String>> getMap(String key) {
Map<String, Map<String, String>> map = config.getMap(id, key);
return map != null ? map : Collections.<String, Map<String, String>> emptyMap();
}
public String getActivator() {
return getString(BldConfig.S_ACTIVATOR);
}
public String getId() {
String name = getString("id");
return name != null ? name : id;
}
public String getVersion() {
String ver = getString(BldConfig.S_VERSION);
if (ver == null) {
ver = BldProject.this.getVersion();
}
return ver;
}
public String getSymbolicName() {
String name = getString(BldConfig.S_SYM_NAME);
return name != null ? name : getId();
}
public List<IPackageExport> getExports() {
ArrayList<IPackageExport> list = new ArrayList<IPackageExport>();
Map<String, Map<String, String>> exports = getMap(BldConfig.M_EXPORTS);
if (exports != null) {
for (String name : exports.keySet()) {
Map<String, String> attrs = exports.get(name);
PackageExport pkgExport = new PackageExport();
pkgExport.setPackageName(name);
String version = attrs.get(BldAttr.VERSION_ATTRIBUTE);
// only default export version from local packages
if (version == null
&& (getSourcePkgs().isEmpty() || getSourcePkgs().contains(name))) {
version = getVersion();
}
if (version != null)
pkgExport.setVersion(new Version(version));
list.add(pkgExport);
}
}
return list;
}
public List<IPackageImport> getImports() {
ArrayList<IPackageImport> list = new ArrayList<IPackageImport>();
for (IPackageImport import1 : getRequirements().childrenOfType(IPackageImport.class)) {
list.add(import1);
}
return list;
}
public List<IRequiredBundle> getRequires() {
ArrayList<IRequiredBundle> list = new ArrayList<IRequiredBundle>();
list.addAll(Arrays.asList(getRequirements().childrenOfType(IRequiredBundle.class)));
for (IBldBundle bundle : getBundles()) {
IRequiredBundle fh = bundle.getFragmentHost();
if (fh != null)
list.remove(fh);
}
return list;
}
public IRequiredBundle getFragmentHost() {
IRequiredBundle fragment = null;
Map<String, Map<String, String>> fragments = getMap(BldConfig.M_FRAGMENT);
if (fragments != null) {
for (String name : fragments.keySet()) {
Map<String, String> attr = fragments.get(name);
String versions = attr.isEmpty() ? null : attr.get(BldAttr.VERSION_ATTRIBUTE);
fragment = new RequiredBundle();
fragment.setSymbolicName(name);
fragment.setVersions(VersionRange.parseVersionRange(versions));
break;
}
}
return fragment;
}
public Map<String, Map<String, String>> getLibs() {
Map<String, Map<String, String>> libs = getMap(BldConfig.M_LIBS);
return (libs != null) ? libs : Collections.<String, Map<String, String>> emptyMap();
}
public List<String> getContents() {
return getList(BldConfig.L_CONTENTS);
}
public List<String> getDownloadContents() {
return getList(BldConfig.L_DL_CONTENTS);
}
public List<String> getComposites() {
ArrayList<String> list = new ArrayList<String>();
for (String composite : getList(BldConfig.L_COMPOSITES)) {
list.add(composite);
}
return list;
}
public Map<String, String> getResources() {
HashMap<String, String> map = new HashMap<String, String>();
List<String> resources = getList(BldConfig.L_RESOURCES);
if (resources != null) {
for (String resource : resources) {
String[] paths = resource.split("=", 2);
String fsPath = (paths.length > 1 ? paths[1] : "");
map.put(paths[0], fsPath);
}
}
return map;
}
public Properties getHeaders() {
Properties headers = config.getProps(id, BldConfig.P_HEADER);
return headers;
}
}
public long getLastModified() {
return lastModified;
}
}