blob: 94a65aea3c16c0f025baa1d2c48f50e0f0083c79 [file] [log] [blame]
package aQute.bnd.make.metatype;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import aQute.bnd.annotation.metatype.*;
import aQute.lib.io.*;
import aQute.lib.osgi.*;
import aQute.lib.osgi.Clazz.MethodDef;
import aQute.lib.tag.*;
import aQute.libg.generics.*;
public class MetaTypeReader extends ClassDataCollector implements Resource {
final Analyzer reporter;
Clazz clazz;
String interfaces[];
Tag metadata = new Tag("metatype:MetaData", new String[] {
"xmlns:metatype", "http://www.osgi.org/xmlns/metatype/v1.1.0" });
Tag ocd = new Tag(metadata, "OCD");
Tag designate = new Tag(metadata, "Designate");
Tag object = new Tag(designate, "Object");
// Resource
String extra;
// One time init
boolean finished;
// Designate
boolean override;
String designatePid;
boolean factory;
// AD
Map<MethodDef, Meta.AD> methods = new LinkedHashMap<MethodDef, Meta.AD>();
// OCD
Annotation ocdAnnotation;
MethodDef method;
public MetaTypeReader(Clazz clazz, Analyzer reporter) {
this.clazz = clazz;
this.reporter = reporter;
}
public void annotation(Annotation annotation) {
try {
Meta.OCD ocd = annotation.getAnnotation(Meta.OCD.class);
Meta.AD ad = annotation.getAnnotation(Meta.AD.class);
if (ocd != null) {
this.ocdAnnotation = annotation;
}
if (ad != null) {
assert method != null;
methods.put(method, ad);
}
} catch (Exception e) {
reporter.error("Error during annotation parsing %s : %s", clazz, e);
e.printStackTrace();
}
}
/**
* @param id
* @param name
* @param cardinality
* @param required
* @param deflt
* @param type
* @param max
* @param min
* @param optionLabels
* @param optionValues
*/
static Pattern COLLECTION = Pattern
.compile("(.*(Collection|Set|List|Queue|Stack|Deque))<(L.+;)>");
private void addMethod(MethodDef method, Meta.AD ad) throws Exception {
// Set all the defaults.
String rtype = method.getReturnType();
String id = Configurable.mangleMethodName(method.name);
String name = Clazz.unCamel(id);
int cardinality = 0;
if (rtype.endsWith("[]")) {
cardinality = Integer.MAX_VALUE;
rtype = rtype.substring(0, rtype.length() - 2);
}
if (rtype.indexOf('<') > 0) {
if (cardinality != 0)
reporter.error(
"AD for %s.%s uses an array of collections in return type (%s), Metatype allows either Vector or array",
clazz.getFQN(), method.name, method.getReturnType());
Matcher m = COLLECTION.matcher(rtype);
if (m.matches()) {
rtype = Clazz.objectDescriptorToFQN(m.group(3));
cardinality = Integer.MIN_VALUE;
}
}
Meta.Type type = getType(rtype);
boolean required = ad ==null || ad.required();
String deflt = null;
String max = null;
String min = null;
String[] optionLabels = null;
String[] optionValues = null;
String description = null;
Clazz c = reporter.findClass(Clazz.fqnToPath(rtype));
if (c != null && c.isEnum()) {
optionValues = parseOptionValues(c);
}
// Now parse the annotation for any overrides
if (ad != null) {
if (ad.id() != null)
id = ad.id();
if (ad.name() != null)
name = ad.name();
if (ad.cardinality() != 0)
cardinality = ad.cardinality();
if (ad.type() != null)
type = ad.type();
if (ad.required() || ad.deflt() == null)
required = true;
if (ad.description() != null)
description = ad.description();
if (ad.optionLabels() != null)
optionLabels = ad.optionLabels();
if (ad.optionValues() != null )
optionValues = ad.optionValues();
if (ad.min() != null)
min = ad.min();
if (ad.max() != null)
max = ad.max();
if (ad.deflt() != null)
deflt = ad.deflt();
}
if (optionValues != null) {
if (optionLabels == null || optionLabels.length == 0) {
optionLabels = new String[optionValues.length];
for (int i = 0; i < optionValues.length; i++)
optionLabels[i] = Clazz.unCamel(optionValues[i]);
}
if (optionLabels.length != optionValues.length) {
reporter.error("Option labels and option values not the same length for %s", id);
optionLabels = optionValues;
}
}
Tag adt = new Tag(this.ocd, "AD");
adt.addAttribute("name", name);
adt.addAttribute("id", id);
adt.addAttribute("cardinality", cardinality);
adt.addAttribute("required", required);
adt.addAttribute("default", deflt);
adt.addAttribute("type", type);
adt.addAttribute("max", max);
adt.addAttribute("min", min);
adt.addAttribute("description", description);
if (optionLabels != null) {
for (int i = 0; i < optionLabels.length; i++) {
Tag option = new Tag(adt, "Option");
option.addAttribute("label", optionLabels[i]);
option.addAttribute("value", optionValues[i]);
}
}
}
private String[] parseOptionValues(Clazz c) throws Exception {
final List<String> values = Create.list();
c.parseClassFileWithCollector(new ClassDataCollector() {
public void field(Clazz.FieldDef def) {
if (def.isEnum()) {
values.add(def.name);
}
}
});
return values.toArray(new String[values.size()]);
}
Meta.Type getType(String rtype) {
if (rtype.endsWith("[]")) {
rtype = rtype.substring(0, rtype.length() - 2);
if (rtype.endsWith("[]"))
throw new IllegalArgumentException("Can only handle array of depth one");
}
if ("boolean".equals(rtype) || Boolean.class.getName().equals(rtype))
return Meta.Type.Boolean;
else if ("byte".equals(rtype) || Byte.class.getName().equals(rtype))
return Meta.Type.Byte;
else if ("char".equals(rtype) || Character.class.getName().equals(rtype))
return Meta.Type.Character;
else if ("short".equals(rtype) || Short.class.getName().equals(rtype))
return Meta.Type.Short;
else if ("int".equals(rtype) || Integer.class.getName().equals(rtype))
return Meta.Type.Integer;
else if ("long".equals(rtype) || Long.class.getName().equals(rtype))
return Meta.Type.Long;
else if ("float".equals(rtype) || Float.class.getName().equals(rtype))
return Meta.Type.Float;
else if ("double".equals(rtype) || Double.class.getName().equals(rtype))
return Meta.Type.Double;
else
return Meta.Type.String;
}
@Override public void method(MethodDef mdef) {
method = mdef;
methods.put(mdef, null);
}
public String getExtra() {
return extra;
}
public long lastModified() {
return 0;
}
public InputStream openInputStream() throws IOException {
final PipedInputStream pin = new PipedInputStream();
final PipedOutputStream pout = new PipedOutputStream(pin);
getExecutor().execute(new Runnable() {
public void run() {
try {
write(pout);
} catch (IOException e) {
// Cause an exception in the other end
IO.close(pin);
}
IO.close(pout);
}
});
return pin;
}
private Executor getExecutor() {
return reporter.getPlugin(Executor.class);
}
public void setExtra(String extra) {
this.extra = extra;
}
public void write(OutputStream out) throws IOException {
try {
finish();
} catch (Exception e) {
throw new RuntimeException(e);
}
PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
pw.println("<?xml version='1.0'?>");
metadata.print(0, pw);
pw.flush();
}
void finish() throws Exception {
if (!finished) {
finished = true;
clazz.parseClassFileWithCollector(this);
Meta.OCD ocd = null;
if (this.ocdAnnotation != null)
ocd = this.ocdAnnotation.getAnnotation(Meta.OCD.class);
else
ocd = Configurable.createConfigurable(Meta.OCD.class,
new HashMap<String, Object>());
// defaults
String id = clazz.getFQN();
String name = Clazz.unCamel(Clazz.getShortName(clazz.getFQN()));
String description = null;
String localization = id;
boolean factory = this.factory;
if (ocd.id() != null)
id = ocd.id();
if (ocd.name() != null)
name = ocd.name();
if (ocd.localization() != null)
localization = ocd.localization();
if (ocd.description() != null)
description = ocd.description();
String pid = id;
if (override) {
pid = this.designatePid;
factory = this.factory;
id = this.designatePid; // for the felix problems
} else {
if (ocdAnnotation.get("factory") != null) {
factory = true;
}
}
this.ocd.addAttribute("name", name);
this.ocd.addAttribute("id", id);
this.ocd.addAttribute("description", description);
this.ocd.addAttribute("localization", localization);
// do ADs
for (Map.Entry<MethodDef, Meta.AD> entry : methods.entrySet())
addMethod(entry.getKey(), entry.getValue());
this.designate.addAttribute("pid", pid);
if (factory)
this.designate.addAttribute("factoryPid", pid);
this.object.addAttribute("ocdref", id);
}
}
public void setDesignate(String pid, boolean factory) {
this.override = true;
this.factory = factory;
this.designatePid = pid;
}
}