package com.atlassian.labs.speakeasy.commonjs;
import com.atlassian.labs.speakeasy.commonjs.util.IterableTreeMap;
import com.atlassian.labs.speakeasy.commonjs.util.JsDoc;
import com.atlassian.labs.speakeasy.commonjs.util.JsDocParser;
import com.atlassian.labs.speakeasy.commonjs.util.ModuleUtil;
import com.google.common.collect.ImmutableSet;
import org.mozilla.javascript.*;
import org.mozilla.javascript.ast.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.lang.Validate.notNull;
/**
*
*/
public class Module
{
@XmlAttribute
private final String id;
@XmlElement
private final JsDoc jsDoc;
private final String path;
private final long lastModified;
@XmlElement
private final Collection<String> dependencies;
@XmlElement
private final Map<String,Export> exports;
private static final Logger log = LoggerFactory.getLogger(Module.class);
public Module(String id, String path, long lastModified, String moduleContents)
{
this.id = id;
this.path = path;
this.lastModified = lastModified;
this.exports = new IterableTreeMap<String,Export>();
if (path.endsWith(".js") || path.endsWith(".host"))
{
Set<String> dependencies = newHashSet();
jsDoc = parseContent(moduleContents, dependencies);
this.dependencies = ImmutableSet.copyOf(dependencies);
}
else if (path.endsWith(".mu"))
{
jsDoc = new JsDoc("Mustache template");
this.dependencies = ImmutableSet.of("speakeasy/mustache");
final Export export = new Export("render", new JsDoc("Renders the template with the provided context"));
exports.put("render", export);
}
else
{
throw new IllegalArgumentException("Invalid module:" + id + " of path:" + path);
}
notNull(jsDoc);
}
private JsDoc parseContent(String moduleContents, final Set<String> dependencies)
{
final AtomicReference<JsDoc> jsDoc = new AtomicReference<JsDoc>(new JsDoc(""));
CompilerEnvirons env = new CompilerEnvirons();
env.setRecordingComments(true);
env.setRecordingLocalJsDocComments(true);
Parser parser = new Parser(env, new ErrorReporter()
{
public void warning(String message, String sourceName, int line, String lineSource, int lineOffset)
{
//To change body of implemented methods use File | Settings | File Templates.
}
public void error(String message, String sourceName, int line, String lineSource, int lineOffset)
{
log.warn("Error parsing module " + id + ":\n" +
" Message: " + message + "\n" +
" Line: " + line + "\n" +
" Line Src:" + lineSource + "\n" +
" Column: " + lineOffset);
throw new RuntimeException("Error parsing module '" + id + "' on line " + line + ": " + message);
}
public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset)
{
return null; //To change body of implemented methods use File | Settings | File Templates.
}
});
try
{
final AstRoot root = parser.parse(new StringReader(moduleContents), path, 1);
if (root.getComments() != null && !root.getComments().isEmpty())
{
final String rawHeaderDocs = root.getComments().first().getValue();
jsDoc.set(JsDocParser.parse(getId(), rawHeaderDocs));
}
root.visitAll(new NodeVisitor()
{
public boolean visit(AstNode node)
{
try
{
if (node.getType() == Token.ASSIGN)
{
Assignment assignment = (Assignment) node;
if (assignment.getLeft().getType() == Token.GETPROP)
{
PropertyGet left = (PropertyGet)assignment.getLeft();
if (left.getLeft() instanceof Name && ((Name)left.getLeft()).getIdentifier().equals("exports"))
{
String exportName = left.getProperty().getIdentifier();
Export export = new Export(exportName, JsDocParser.parse(getId(), node.getJsDoc()));
if (jsDoc.get().getDescription().length() > 0 && export.getJsDoc().getDescription().equals(jsDoc.get().getDescription()))
{
jsDoc.set(new JsDoc(""));
}
exports.put(exportName, export);
}
}
}
else if (node.getType() == Token.CALL)
{
FunctionCall call = (FunctionCall)node;
if (call.getTarget().getType() == Token.NAME)
{
Name name = (Name)call.getTarget();
if ("require".equals(name.getIdentifier()))
{
if (call.getArguments().size() == 1 && call.getArguments().get(0).getType() == Token.STRING)
{
dependencies.add(ModuleUtil.resolveModuleId(id, ((StringLiteral) call.getArguments().get(0)).getValue()));
}
}
}
}
return true;
}
catch (RuntimeException ex)
{
throw new IllegalArgumentException("Exception while parsing for exports in file " + root.getSourceName() + " on line " + node.getLineno(), ex);
}
}
});
}
catch (IOException e)
{
log.warn("Unable to determine exports", e);
}
return jsDoc.get();
}
public Map<String,Export> getExports()
{
return exports;
}
public String getId()
{
return id;
}
public Collection<String> getDependencies()
{
return dependencies;
}
public String getPath()
{
return path;
}
public long getLastModified()
{
return lastModified;
}
public JsDoc getJsDoc()
{
return jsDoc;
}
}