package jfun.yan.xml;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import jfun.util.dict.Dict;
import jfun.yan.ParameterBinder;
import jfun.yan.PropertyBinder;
import jfun.yan.util.resource.ResourceLoader;
import jfun.yan.util.resource.ResourceLoaders;
final class Compiler extends Constants{
private static final Set reserved_tagnames = MyUtil.getNameSet(
new String[]{SEQUENCE, LOCAL, BINDER, FUNCTION, CALLCC, EXPAND, MACRO}
);
private static final Set reserved_keys = MyUtil.getNameSet(
new String[]{NULL}
);
private final Interpreter interpreter;
private static final Set module_attributes =
MyUtil.getNameSet(new String[]{NAME, DESCRIPTION, DEPENDS, EXPORT, HIDE});
private static final Set body_attributes =
MyUtil.getNameSet(new String[]{AUTOWIRE, SINGLETON, EAGER_INSTANTIATED, EAGER_INSTANTIATED2});
private final String list_separator = ",";
private final String map_separator = ",";
Compiler(Interpreter interpreter) {
this.interpreter = interpreter;
}
private Set getExportKeys(String export){
final String[] export_names = NutsUtils.split(export, list_separator);
final HashSet result = new HashSet();
for(int i=0; i<export_names.length; i++){
final String key = export_names[i];
result.add(key);
}
return result;
}
//null is returned for wildcard
private String[] compileFilterKeys(String keys, Location loc){
keys = keys.trim();
if(WILDCARD.equals(keys)){
return null;
}
final String[] result = NutsUtils.split(keys, list_separator);
final Set cache = new HashSet();
for(int i=0; i<result.length; i++){
final String name = result[i];
if(cache.contains(name)){
throw new ConfigurationException("<" + IMPORT + "> - duplicate name: "
+ name, loc);
}
if(WILDCARD.equals(name)){
throw new ConfigurationException("<" + IMPORT + "> - wildcard cannot be combined with other keys",
loc);
}
if(name.endsWith(WILDCARD)){
final String name0 = name.substring(0, name.length()-1);
checkIdentifier(name0, loc);
}
else{
checkIdentifier(name, loc);
}
cache.add(name);
}
return result;
}
private Filter getExportFilter(String exports, String hidden, Location loc){
if(exports==null){
exports = "*";
}
return getKeyFilter(null, exports, hidden, loc);
}
private Filter getKeyFilter(String prefix, String includes,
String excludes,
Location loc){
if(includes==null){
//include nothing.
return new Filter(new String[0],
excludes==null?new String[0]: compileFilterKeys(excludes, loc));
}
includes = includes.trim();
if(excludes==null){
excludes = "";
}
return new Filter(compileFilterKeys(includes, loc), compileFilterKeys(excludes, loc));
}
private void checkImport(Import imp, IdChecker idchecker, Location loc){
String prefix = imp.getPrefix();
final String[] keys = imp.getKeys();
for(int i=0; i<keys.length; i++){
final String key = keys[i];
idchecker.checkId(prefix+key, loc);
}
}
private Module compileImportedModule(Tag tag)
throws IOException, CyclicModuleDependencyException{
final Location loc = tag.getLocation();
final String resource = tag.getAttribute(RESOURCE);
if(resource != null){
MyUtil.assertAttributes(tag, MyUtil.getNameSet(new String[]{
RESOURCE, CLASSPATH, NAMESPACE, INCLUDES, EXCLUDES
}));
final ClassLoader cloader = getClassLoader(tag);
return interpreter.interpretResource(getResourceLoader(cloader), resource, null);
}
else{
MyUtil.assertAttributes(tag, MyUtil.getNameSet(new String[]{
FILE, NAMESPACE, INCLUDES, EXCLUDES
}));
final String filename = tag.getAttribute(FILE);
if(filename==null)
throw new ConfigurationException("either " + RESOURCE+ " or " + FILE
+" has to be specified for <"
+ IMPORT + ">", loc);
return interpreter.interpretFile(filename, loc);
}
}
private Import compileImportTag(Tag tag, IdChecker checker)
throws IOException, CyclicModuleDependencyException{
final String includes_str = MyUtil.getMandatory(tag, INCLUDES);
final String excludes_str = tag.getAttribute(EXCLUDES);
final String namespace = tag.getAttribute(NAMESPACE);
final String prefix = namespace==null?"":namespace+".";
final Filter imports = getKeyFilter(prefix, includes_str, excludes_str,
tag.getLocation());
final Module imported = compileImportedModule(tag);
final Import ret =
new Import(prefix, imports, imported, tag.getLocation());
checkImport(ret, checker, tag.getLocation());
return ret;
}
private static void checkIdentifier(String id, Location loc){
if(reserved_keys.contains(id)){
throw new ConfigurationException("\""+id+"\""
+ " is reserved.", loc);
}
if(!NutsUtils.isValidId(id)){
throw new ConfigurationException("\""+id+"\""
+ " is not a valid id.", loc);
}
}
private static final class GlobalIdChecker implements IdChecker{
private final Location dep_loc;
private final Set dependencies;
private final HashMap imports = new HashMap();
private boolean importing = true;
GlobalIdChecker(String[] dependencies, Location loc){
this.dependencies = MyUtil.getNameSet(dependencies);
this.dep_loc = loc;
}
public void checkId(String id, Location loc){
checkIdentifier(id, loc);
if(dependencies.contains(id)){
throw new ConfigurationException("\""+id+"\""
+ " duplicates with declared dependency name.", loc);
}
final Location duploc = (Location)imports.get(id);
if(duploc!=null){
throw new ConfigurationException("\""+id+"\"" +
" duplicates with an imported name at line "
+ duploc.getLineNo(), loc);
}
if(importing)
imports.put(id, loc);
}
void importDone(){
importing = false;
}
}
private static void checkMandatories(Tag tag, String[] names){
for(int i=0; i<names.length; i++){
final String name = names[i];
MyUtil.getMandatory(tag, name);
}
}
private void populateNutTag(Tag tag, Map nuts){
final Location subloc = tag.getLocation();
try{
populateNut(nuts, tag);
}
catch(NoClassDefFoundError e){
throw new ConfigurationException(e, subloc);
}
catch(ClassNotFoundException e){
throw new ConfigurationException("nut class not found: "+e.getMessage(), subloc);
}
catch(IntrospectionException e){
throw new ConfigurationException("invalid nut class: "+e.getMessage(), subloc);
}
}
Module compileModule(Object id, Node node){
if(node instanceof Tag){
return compileModuleTag(id, (Tag)node);
}
else{
throw new ConfigurationException("tag expected", node.getLocation());
}
}
private static Dict seedImports(Dict ctxt, Import[] imports){
final ArrayList keys = new ArrayList();
final ArrayList stmts = new ArrayList();
for(int i=0; i<imports.length; i++){
final Import imp = imports[i];
final String[] exports = imp.getKeys();
final String prefix = imp.getPrefix();
for(int j=0; j<exports.length; j++){
final String key = prefix+exports[j];
keys.add(key);
stmts.add(new Bound(key, imp.getLocation()));
}
}
return ctxt.puts(keys.toArray(), stmts.toArray());
}
Module compileModuleTag(Object id, Tag tag){
final Location loc = tag.getLocation();
if(!MODULE.equals(tag.getName())){
throw new ConfigurationException("the top level tag has to be "
+MODULE, loc);
}
MyUtil.assertAttributes(tag, module_attributes);
checkMandatories(tag, new String[]{NAME});
final String name = tag.getAttribute(NAME);
final String desc = tag.getAttribute(DESCRIPTION);
final String export_str = tag.getAttribute(EXPORT);
final String hide_str = tag.getAttribute(HIDE);
final Filter filter = getExportFilter(export_str, hide_str, loc);
final StringPredicate exports = filter.getPredicate();//getExported(export);
final String[] dependencies = getDependencies(tag.getAttribute(DEPENDS),
tag.getLocation());
final GlobalIdChecker idchecker = new GlobalIdChecker(dependencies,
tag.getLocation());
final List subnodes = tag.getSubNodes();
final ArrayList importlist = new ArrayList();
final HashMap nuts = new HashMap();
final int nodecount = subnodes.size();
int nodenum = 0;
for(; nodenum<nodecount; nodenum++){
final Node n = (Node)subnodes.get(nodenum);
if(n instanceof Tag){
final Tag t = (Tag)n;
final String tagname = t.getName();
if(IMPORT.equals(tagname)){
try{
importlist.add(compileImportTag(t, idchecker));
}
catch(IOException e){
throw new ConfigurationException("import failed. "+e.getMessage(),
t.getLocation());
}
catch(CyclicModuleDependencyException e){
e.push(n.getLocation());
throw e;
}
continue;
}
else if(NUT.equals(tagname)){
populateNutTag(t, nuts);
continue;
}
}
break;
}
idchecker.importDone();
//load standard nuts
loadNuts(nuts);
final Import[] imports = new Import[importlist.size()];
importlist.toArray(imports);
final Statements stmts =
compileModuleStatements(id, tag, dependencies,
imports, nodenum, nuts, idchecker);
return new ModuleBuilder(id, name, desc,
dependencies, imports, stmts, exports)
.build(interpreter, interpreter.getFrame());
}
private void loadNuts(final HashMap nuts) {
final Map external_nuts = interpreter.getExternalNuts();
for(Iterator it=external_nuts.keySet().iterator(); it.hasNext();){
final Object key = it.next();
if(nuts.containsKey(key)) continue;
nuts.put(key, external_nuts.get(key));
}
final String properties_file = "jfun/yan/xml/nuts/nuts.properties";
try{
loadNuts(getClass().getClassLoader(),
properties_file, nuts);
}
catch(Exception e){
e.printStackTrace();
throw new IllegalStateException("could not load " + properties_file);
}
}
private Statements compileModuleStatements(Object module_id, Tag tag,
final String[] dependencies,
final Import[] imports, int nodenum,
final HashMap nuts, final GlobalIdChecker idchecker) {
final List subnodes = tag.getSubNodes();
final int nodecount = subnodes.size();
if(nodenum < nodecount){
final Node node = (Node)subnodes.get(nodenum);
if(node instanceof Tag){
final Tag t = (Tag)node;
final String tagname = t.getName();
if(BODY.equals(tagname)){
final Statements body =
compileBody(module_id, t, dependencies, imports, nuts,
tag.getLocation(), idchecker);
if(nodenum < nodecount-1){
throw new ConfigurationException("<"+BODY+"> should be the last sub-element of <"+MODULE+">",
t.getLocation());
}
return body;
}
else{
throw new ConfigurationException("unknown tag <"+t.getName()+">",
t.getLocation());
}
}
else{
throw new ConfigurationException("character data no supported.",
node.getLocation());
}
}
return new Statements(new String[0], new Stmt[0]);
}
private Statements compileBody(final Object module_id,
final Tag tag, final String[] dependencies,
final Import[] imports, final HashMap nuts, final Location loc, final GlobalIdChecker idchecker) {
final Stmt[] deps = new Stmt[dependencies.length];
for(int i=0; i<dependencies.length; i++){
deps[i] = new Bound(dependencies[i], loc);
}
Dict ctxt = interpreter.getInitialCompileContext()
.puts(dependencies, deps);
ctxt = seedImports(ctxt, imports);
return compileBody(module_id, nuts, tag, idchecker,
ctxt);
}
private boolean parseEagerMode(Tag tag){
final String eager_mode_name = MyUtil.getEagerMode(tag);
if(eager_mode_name==null) return false;
final Boolean result = NutsUtils.toBoolean(eager_mode_name);
if(result==null)
throw new ConfigurationException("Unrecognized eager init value: "+eager_mode_name,
tag.getLocation());
return result.booleanValue();
}
private Statements compileBody(
Object module_id, Map nuts, Tag tag,
IdChecker idchecker, Dict initial_ctxt){
MyUtil.assertAttributes(tag, body_attributes);
final String autowire = tag.getAttribute(AUTOWIRE);
final Location loc = tag.getLocation();
final ParameterBinder param_wiring = MyUtil.getParamWiring(autowire,
interpreter.getCustomWiringModes(), loc,
interpreter.getParameterWiring());
final PropertyBinder prop_wiring = MyUtil.getPropWiring(autowire,
interpreter.getCustomWiringModes(), loc,
interpreter.getPropertyWiring());
final SingletonMode singleton =
MyUtil.getSingletonStrategy(tag.getAttribute(SINGLETON), loc,
interpreter.getSingletonMode());
final boolean default_eager_mode = parseEagerMode(tag);
//final Runtime runtime = new Runtime(manager, cloader);
final Statements stmts = new BodyCompiler(interpreter, module_id,
//interpreter.getClassloader(),
//interpreter.getLifecycleManager(),
//interpreter.getBaseDir(),
nuts, list_separator, map_separator,
new WiringMode(param_wiring, prop_wiring, singleton),
//interpreter.getCustomWiringModes(),
//interpreter.getServices(),
reserved_keys,
default_eager_mode)
.compileStatements(tag, initial_ctxt, idchecker,
tag.getName(), tag.getSubNodes());
return stmts;
}
private String[] getDependencies(String s, Location loc){
if(s==null) return new String[0];
final String[] keys = NutsUtils.split(s, list_separator);
final HashSet buf = new HashSet(keys.length);
for(int i=0; i<keys.length;i++){
final String key = keys[i];
if(reserved_keys.contains(key)){
throw new ConfigurationException("dependency name reserved: "+key, loc);
}
if(buf.contains(key)){
throw new ConfigurationException("duplicate dependency name: "+key, loc);
}
checkIdentifier(key, loc);
}
return keys;
}
private ClassLoader getClassLoader(Tag tag){
final String classpath = tag.getAttribute(CLASSPATH);
final ClassLoader cloader = getClass().getClassLoader();
try{
return NutsUtils.getClassLoader(cloader, classpath,
interpreter.getBaseDir());
}
catch(MalformedURLException e){
throw new ConfigurationException("invalid classpath",
tag.getLocation());
}
}
private void populateNut(Map nuts, Tag tag)
throws ClassNotFoundException, IntrospectionException{
final String nutname = MyUtil.getMandatory(tag, NAME);
final String classname = MyUtil.getMandatory(tag, CLASS);
final Location loc = tag.getLocation();
if(reserved_tagnames.contains(nutname)){
throw new ConfigurationException("nut name "+nutname+" is reserved, try a different name.",
loc);
}
if(nuts.containsKey(nutname)){
throw new ConfigurationException("nut name "+nutname+" is already used, try a different name.",
loc);
}
//we use a separate class loader than the one used to load components.
final ClassLoader cloader = getClassLoader(tag);
final Class nutclass = cloader.loadClass(classname);
nuts.put(nutname, interpreter.getIntrospector().getNutDescriptor(nutclass));
}
private StringPredicate getExported(String export){
if(export==null || WILDCARD.equals(export.trim())){
return StringPredicates.always();
}
else{
final Set exports = getExportKeys(export);
return StringPredicates.in(exports);
}
}
private ResourceLoader getResourceLoader(ClassLoader cloader){
return ResourceLoaders.or(cloader, interpreter.getResourceLoader());
}
private void loadNuts(ClassLoader loader,
String resource, Map nuts)
throws IOException, ClassNotFoundException, IntrospectionException{
final ResourceLoader rloader = getResourceLoader(loader);
final InputStream in = rloader.getResourceAsStream(resource);
if(in == null)
throw new IllegalStateException("could not find "
+ resource);
try{
final Properties props = new Properties();
props.load(in);
for(Iterator it=props.keySet().iterator(); it.hasNext();){
final String key = (String)it.next();
if(nuts.containsKey(key))
continue;
String classname = props.getProperty(key);
if(classname.indexOf('.')<0){
classname = "jfun.yan.xml.nuts."+classname;
}
final Class nutclass = loader.loadClass(classname);
nuts.put(key, interpreter.getIntrospector().getNutDescriptor(nutclass));
}
}
finally{
in.close();
}
}
}