/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.client.cache;
import com.eas.script.JsDoc;
import com.eas.script.ParsedJs;
import com.eas.script.Scripts;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.Source;
/**
* Implementation service support for script related tasks.
*
* @author pk, mg refactoring
*/
public class ScriptDocument {
public static class ModuleDocument {
protected final List<JsDoc.Tag> moduleAnnotations = new ArrayList<>();
/**
* User roles that have access to all module's functions, if empty all
* users allowed
*/
protected final Set<String> moduleAllowedRoles = new HashSet<>();
/**
* Roles that have access to specific properties, if empty all users are
* allowed
*/
protected final Map<String, Set<String>> propertyAllowedRoles = new HashMap<>();
protected final Map<String, Set<JsDoc.Tag>> propertyAnnotations = new HashMap<>();
/**
* Functions that may be accessed over network via RPC
*/
protected final Set<String> functionProperties = new HashSet<>();
public ModuleDocument() {
super();
}
public Set<String> getAllowedRoles() {
return Collections.unmodifiableSet(moduleAllowedRoles);
}
public Map<String, Set<String>> getPropertyAllowedRoles() {
return Collections.unmodifiableMap(propertyAllowedRoles);
}
public List<JsDoc.Tag> geŠµAnnotations() {
return Collections.unmodifiableList(moduleAnnotations);
}
public Map<String, Set<JsDoc.Tag>> getPropertyAnnotations() {
return Collections.unmodifiableMap(propertyAnnotations);
}
public boolean hasAnnotation(String anAnnotation) {
return moduleAnnotations.stream().anyMatch((JsDoc.Tag aTag) -> {
return aTag.getName().equalsIgnoreCase(anAnnotation);
});
}
public List<JsDoc.Tag> getAnnotations() {
return Collections.unmodifiableList(moduleAnnotations);
}
public JsDoc.Tag getAnnotation(String anAnnotation) {
Optional<JsDoc.Tag> found = moduleAnnotations.stream().filter((JsDoc.Tag aTag) -> {
return aTag.getName().equalsIgnoreCase(anAnnotation);
}).findAny();
return found.isPresent() ? found.get() : null;
}
public Set<String> getFunctionProperties() {
return Collections.unmodifiableSet(functionProperties);
}
void parseAnnotations(String commentText) {
JsDoc jsDoc = new JsDoc(commentText);
jsDoc.parseAnnotations();
jsDoc.getAnnotations().stream().forEach((JsDoc.Tag tag) -> {
moduleAnnotations.add(tag);
if (tag.getName().equalsIgnoreCase(JsDoc.Tag.ROLES_ALLOWED_TAG)) {
tag.getParams().stream().forEach((role) -> {
moduleAllowedRoles.add(role);
});
}
});
}
private void readPropertyRoles(String aPropertyName, String aJsDocBody) {
if (aJsDocBody != null) {
JsDoc jsDoc = new JsDoc(aJsDocBody);
jsDoc.parseAnnotations();
jsDoc.getAnnotations().stream().forEach((JsDoc.Tag tag) -> {
Set<JsDoc.Tag> tags = propertyAnnotations.get(aPropertyName);
if (tags == null) {
tags = new HashSet<>();
propertyAnnotations.put(aPropertyName, tags);
}
tags.add(tag);
if (tag.getName().equalsIgnoreCase(JsDoc.Tag.ROLES_ALLOWED_TAG)) {
Set<String> roles = propertyAllowedRoles.get(aPropertyName);
if (roles == null) {
roles = new HashSet<>();
}
for (String role : tag.getParams()) {
roles.add(role);
}
propertyAllowedRoles.put(aPropertyName, roles);
}
});
}
}
}
private final Map<String, ModuleDocument> modules = new HashMap<>();
protected ScriptDocument() {
super();
}
public static ScriptDocument parse(String aSource, String aName) {
ScriptDocument doc = new ScriptDocument();
doc.readScriptModules(aSource, aName);
return doc;
}
public Map<String, ModuleDocument> getModules() {
return Collections.unmodifiableMap(modules);
}
/**
* Reads script annotations. Annotations, accompanied with
*
* @name annotation are the 'module annotations'. Annotations, followed by
* any property assignment are the 'property annotations'. Property
* annotations will be taken into account while accessing through modules.
* @param aSource
*/
private void readScriptModules(String aSource, String aDefaultModuleName) {
assert aSource != null : "JavaScript source can't be null";
Source source = Source.sourceFor(aDefaultModuleName, aSource);
ParsedJs parsed = Scripts.parseJs(aSource);
if (parsed != null) {
LexicalContext context = new LexicalContext();
FunctionNode ast = parsed.getAst();
Map<Long, Long> prevComments = parsed.getPrevComments();
ast.accept(new NodeVisitor(context) {
protected final int GLOBAL_CONSTRUCTORS_BODY_SCOPE_LEVEL = 2;
protected final int AMD_CONSTRUCTORS_BODY_SCOPE_LEVEL = 3;
private int scopeLevel;
@Override
public boolean enterCallNode(CallNode callNode) {
Expression func = callNode.getFunction();
if (func instanceof AccessNode) {
AccessNode an = (AccessNode) func;
if (DEFINE_FUNC_NAME.equals(an.getProperty())) {
defineCall(callNode, an.getBase().getToken());
}
} else if (func instanceof IdentNode) {
IdentNode in = (IdentNode) func;
if (DEFINE_FUNC_NAME.equals(in.getName())) {
defineCall(callNode, in.getToken());
}
}
return super.enterCallNode(callNode);
}
private static final String DEFINE_FUNC_NAME = "define";
protected CallNode amdDefineCall;
private void defineCall(CallNode callNode, long aCommentableToken) {
amdDefineCall = callNode;
String moduleName;
if (callNode.getArgs().size() == 3 && callNode.getArgs().get(0) instanceof LiteralNode
&& ((LiteralNode) callNode.getArgs().get(0)).getType().isString()) {
moduleName = ((LiteralNode) callNode.getArgs().get(0)).getString();
} else {
moduleName = null;
}
mdModule = new ModuleDocument();
Set<String> allowedRoles = new HashSet<>();
if (prevComments.containsKey(aCommentableToken)) {
long prevComment = prevComments.get(aCommentableToken);
String defineCallComment = source.getString(prevComment);
mdModule.parseAnnotations(defineCallComment);
}
mdModule.moduleAllowedRoles.addAll(allowedRoles);
if (moduleName == null || moduleName.isEmpty()) {
moduleName = aDefaultModuleName;
}
if (modules.containsKey(moduleName)) {
Logger.getLogger(ScriptDocument.class.getName()).log(Level.WARNING, "Module with name \"{0}\" ia already defined in script {1}.", new Object[]{moduleName != null ? moduleName : "null", aDefaultModuleName});
}
modules.put(moduleName, mdModule);
}
@Override
public Node leaveCallNode(CallNode callNode) {
if (callNode == amdDefineCall) {
amdDefineCall = null;
mdModule = null;
}
return super.leaveCallNode(callNode);
}
@Override
public boolean enterVarNode(VarNode varNode) {
if (mdConstructor != null && context.getCurrentFunction() == mdConstructor) {
if (varNode.getAssignmentSource() instanceof IdentNode) {
IdentNode in = (IdentNode) varNode.getAssignmentSource();
if (Scripts.THIS_KEYWORD.equals(in.getName())) {
mdThisAliases.add(varNode.getAssignmentDest().getName());
}
}
}
return super.enterVarNode(varNode);
}
@Override
public boolean enterBinaryNode(BinaryNode binaryNode) {
// For global scope modules
if (scopeLevel == GLOBAL_CONSTRUCTORS_BODY_SCOPE_LEVEL && binaryNode.isAssignment() && !binaryNode.isSelfModifying()) {
if (binaryNode.getAssignmentDest() instanceof AccessNode) {
AccessNode left = (AccessNode) binaryNode.getAssignmentDest();
if (left.getBase() instanceof IdentNode && mdThisAliases.contains(((IdentNode) left.getBase()).getName())) {
long ft = left.getBase().getToken();
String comment = null;
if (prevComments.containsKey(ft)) {
long prevComment = prevComments.get(ft);
comment = source.getString(prevComment);
}
processProperty(left.getProperty(), binaryNode.getAssignmentSource(), comment);
}
}
}
// For AMD modules
if (scopeLevel == AMD_CONSTRUCTORS_BODY_SCOPE_LEVEL && binaryNode.isAssignment() && !binaryNode.isSelfModifying()) {
if (binaryNode.getAssignmentDest() instanceof AccessNode) {
AccessNode left = (AccessNode) binaryNode.getAssignmentDest();
if (left.getBase() instanceof IdentNode) {
long ft = left.getBase().getToken();
String comment = null;
if (prevComments.containsKey(ft)) {
long prevComment = prevComments.get(ft);
comment = source.getString(prevComment);
}
processProperty(left.getProperty(), binaryNode.getAssignmentSource(), comment);
}
}
}
return super.enterBinaryNode(binaryNode);
}
protected ModuleDocument mdModule;// current module form nearest scope
protected FunctionNode mdConstructor;// current AMD/GMD constructor
protected Set<String> mdThisAliases = new HashSet<String>() {
{
add(Scripts.THIS_KEYWORD);
}
};
@Override
public boolean enterFunctionNode(FunctionNode functionNode) {
scopeLevel++;
if (!functionNode.isAnonymous() && scopeLevel == GLOBAL_CONSTRUCTORS_BODY_SCOPE_LEVEL) {
mdModule = new ModuleDocument();
mdConstructor = functionNode;
long ft = functionNode.getFirstToken();
if (prevComments.containsKey(ft)) {
long prevComment = prevComments.get(ft);
String commentText = source.getString(prevComment);
mdModule.parseAnnotations(commentText);
}
modules.put(functionNode.getName(), mdModule);
}
return super.enterFunctionNode(functionNode);
}
@Override
public Node leaveFunctionNode(FunctionNode functionNode) {
if (functionNode == mdConstructor) {
mdConstructor = null;
mdModule = null;
mdThisAliases = new HashSet<String>() {
{
add(Scripts.THIS_KEYWORD);
}
};
}
scopeLevel--;
return super.leaveFunctionNode(functionNode);
}
protected void processProperty(String aPropertyName, Expression aValue, String aComment) {
if (mdModule != null) {
if (!aPropertyName.contains(".")) {
mdModule.functionProperties.add(aPropertyName);
if (aComment != null) {
mdModule.readPropertyRoles(aPropertyName, aComment);
}
}
}
}
});
}
}
}