All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.stjs.generator.writer.declaration.ClassWriter Maven / Gradle / Ivy

package org.stjs.generator.writer.declaration;

import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;

import org.stjs.generator.GenerationContext;
import org.stjs.generator.GeneratorConstants;
import org.stjs.generator.javac.AnnotationHelper;
import org.stjs.generator.javac.ElementUtils;
import org.stjs.generator.javac.TreeUtils;
import org.stjs.generator.javac.TreeWrapper;
import org.stjs.generator.javac.TypesUtils;
import org.stjs.generator.javascript.AssignOperator;
import org.stjs.generator.javascript.JavaScriptBuilder;
import org.stjs.generator.javascript.Keyword;
import org.stjs.generator.javascript.NameValue;
import org.stjs.generator.javascript.UnaryOperator;
import org.stjs.generator.name.DependencyType;
import org.stjs.generator.utils.JavaNodes;
import org.stjs.generator.writer.JavascriptKeywords;
import org.stjs.generator.writer.MemberWriters;
import org.stjs.generator.writer.WriterContributor;
import org.stjs.generator.writer.WriterVisitor;
import org.stjs.javascript.annotation.ServerSide;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;

public class ClassWriter implements WriterContributor {

	/**
	 * generate the namespace declaration stjs.ns("namespace") if needed
	 */
	private void addNamespace(ClassTree tree, GenerationContext context, List stmts) {
		Element type = TreeUtils.elementFromDeclaration(tree);
		if (JavaNodes.isInnerType(type)) {
			// this is an inner (anonymous or not) class - no namespace declaration is generated
			return;
		}
		String namespace = context.getCurrentWrapper().getNamespace();
		if (!namespace.isEmpty()) {
			JavaScriptBuilder js = context.js();
			JS target = js.property(js.name(GeneratorConstants.STJS), "ns");
			stmts.add(js.expressionStatement(js.functionCall(target, Collections.singleton(js.string(namespace)))));
		}
	}

	/**
	 * @return the node to put in the super class. for intefaces, the super class goes also in the interfaces list
	 */
	private JS getSuperClass(ClassTree clazz, GenerationContext context) {
		Element type = TreeUtils.elementFromDeclaration(clazz);
		if (clazz.getExtendsClause() == null || type.getKind() == ElementKind.INTERFACE) {
			// no super class found
			return context.js().keyword(Keyword.NULL);
		}

		TreeWrapper superType = context.getCurrentWrapper().child(clazz.getExtendsClause());
		if (superType.isSyntheticType()) {
			return context.js().keyword(Keyword.NULL);
		}

		DependencyType depType = getDependencyTypeForClassDef(type);
		return context.js().name(superType.getTypeName(depType));
	}

	private DependencyType getDependencyTypeForClassDef(Element type) {
		if (JavaNodes.isInnerType(type)) {
			// this is an inner (anonymous or not) class -> STATIC dep type instead
			return DependencyType.STATIC;
		}
		return DependencyType.EXTENDS;
	}

	/**
	 *
	 @return the list of implemented interfaces. for intefaces, the super class goes also in the interfaces list
	 */
	private JS getInterfaces(ClassTree clazz, GenerationContext context) {
		Element type = TreeUtils.elementFromDeclaration(clazz);
		DependencyType depType = getDependencyTypeForClassDef(type);

		List ifaces = new ArrayList();
		for (Tree iface : clazz.getImplementsClause()) {
			TreeWrapper ifaceType = context.getCurrentWrapper().child(iface);
			if (!ifaceType.isSyntheticType()) {
				ifaces.add(context.js().name(ifaceType.getTypeName(depType)));
			}
		}

		if (clazz.getExtendsClause() != null && type.getKind() == ElementKind.INTERFACE) {
			TreeWrapper superType = context.getCurrentWrapper().child(clazz.getExtendsClause());
			if (!superType.isSyntheticType()) {
				ifaces.add(0, context.js().name(superType.getTypeName(DependencyType.EXTENDS)));
			}
		}
		return context.js().array(ifaces);
	}

	/**
	 * @return the JavaScript node for the class' constructor
	 */
	private JS getConstructor(WriterVisitor visitor, ClassTree clazz, GenerationContext context) {
		for (Tree member : clazz.getMembers()) {
			if (JavaNodes.isConstructor(member)) {
				// TODO skip the "native" constructors
				JS node = visitor.scan(member, context);
				if (node != null) {
					return node;
				}
			}
		}
		// no constructor found : interfaces, return an empty function
		return context.js().function(null, Collections. emptyList(), null);
	}

	private List getAllMembersExceptConstructors(ClassTree clazz) {
		List nonConstructors = new ArrayList();
		for (Tree member : clazz.getMembers()) {
			if (!JavaNodes.isConstructor(member) && !(member instanceof BlockTree)) {
				nonConstructors.add(member);
			}
		}
		return nonConstructors;
	}

	/**
	 * @return the JavaScript node for the class' members
	 */
	private JS getMembers(WriterVisitor visitor, ClassTree clazz, GenerationContext context) {
		// the following members must not appear in the initializer function:
		// - constructors (they are printed elsewhere)
		// - abstract methods (they should be omitted)

		List nonConstructors = getAllMembersExceptConstructors(clazz);

		if (nonConstructors.isEmpty()) {
			return context.js().keyword(Keyword.NULL);
		}
		@SuppressWarnings("unchecked")
		List params = Arrays.asList(context.js().name(JavascriptKeywords.CONSTRUCTOR), context.js().name(JavascriptKeywords.PROTOTYPE));

		List stmts = new ArrayList();
		for (Tree member : nonConstructors) {
			stmts.add(visitor.scan(member, context));
		}

		return context.js().function(null, params, context.js().block(stmts));
	}

	private void addStaticInitializers(WriterVisitor visitor, ClassTree tree, GenerationContext context, List stmts) {
		for (Tree member : tree.getMembers()) {
			if (member instanceof BlockTree) {
				stmts.add(visitor.scan(member, context));
			}
		}
	}

	public static boolean isMainMethod(MethodTree method) {
		if (JavaNodes.isStatic(method) && "main".equals(method.getName().toString()) && method.getParameters().size() == 1) {
			VariableElement var = TreeUtils.elementFromDeclaration(method.getParameters().get(0));
			if (var.asType() instanceof ArrayType) {
				TypeMirror componentType = ((ArrayType) var.asType()).getComponentType();
				return TypesUtils.isString(componentType);
			}
		}
		return false;
	}

	private boolean hasMainMethod(ClassTree clazz) {
		for (Tree member : clazz.getMembers()) {
			if (!(member instanceof MethodTree)) {
				continue;
			}
			MethodTree method = (MethodTree) member;

			if (isMainMethod(method)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * add the call to the main method, if it exists
	 */
	private void addMainMethodCall(ClassTree clazz, List stmts, GenerationContext context) {
		if (!hasMainMethod(clazz)) {
			return;
		}
		TypeElement type = TreeUtils.elementFromDeclaration(clazz);
		JS target = context.getCurrentWrapper().isGlobal() ? null : context.js().name(
				context.getNames().getTypeName(context, type, DependencyType.STATIC));

		JavaScriptBuilder js = context.js();
		JS condition = js.unary(UnaryOperator.LOGICAL_COMPLEMENT, js.property(js.name(GeneratorConstants.STJS), "mainCallDisabled"));
		JS thenPart = js.expressionStatement(js.functionCall(js.property(target, "main"), Collections. emptyList()));
		stmts.add(js.ifStatement(condition, thenPart, null));
	}

	@SuppressWarnings("unchecked")
	private JS getFieldTypeDesc(TypeMirror type, GenerationContext context) {
		JavaScriptBuilder js = context.js();
		if (JavaNodes.isJavaScriptPrimitive(type)) {
			return js.keyword(Keyword.NULL);
		}
		JS typeName = js.string(context.getNames().getTypeName(context, type, DependencyType.OTHER));

		if (type instanceof DeclaredType) {
			DeclaredType declaredType = (DeclaredType) type;

			// enum
			if (declaredType.asElement().getKind() == ElementKind.ENUM) {
				return js.object(Arrays.asList(NameValue.of("name", js.string("Enum")),
						NameValue.of("arguments", js.array(Collections.singleton(typeName)))));
			}
			// parametrized type
			if (!declaredType.getTypeArguments().isEmpty()) {
				List array = new ArrayList();
				for (TypeMirror arg : declaredType.getTypeArguments()) {
					array.add(getFieldTypeDesc(arg, context));
				}
				return js.object(Arrays.asList(NameValue.of("name", typeName), NameValue.of("arguments", js.array(array))));
			}

		}

		return typeName;
	}

	@SuppressWarnings("unused")
	private JS getTypeDescription(WriterVisitor visitor, ClassTree tree, GenerationContext context) {
		// if (isGlobal(type)) {
		// printer.print(JavascriptKeywords.NULL);
		// return;
		// }

		TypeElement type = TreeUtils.elementFromDeclaration(tree);

		List> props = new ArrayList>();
		for (Element member : ElementUtils.getAllFieldsIn(type)) {
			TypeMirror memberType = ElementUtils.getType(member);
			if (JavaNodes.isJavaScriptPrimitive(memberType)) {
				continue;
			}
			if (member.getKind() == ElementKind.ENUM_CONSTANT) {
				continue;
			}
			if (memberType instanceof TypeVariable) {
				// what to do with fields of generic parameters !?
				continue;
			}
			if (!skipTypeDescForField(member)) {
				props.add(NameValue.of(member.getSimpleName(), getFieldTypeDesc(memberType, context)));
			}
		}
		return context.js().object(props);
	}

	private boolean skipTypeDescForField(Element member) {
		if (((TypeElement) member.getEnclosingElement()).getQualifiedName().toString().startsWith("java.lang.")) {
			// maybe we should rather skip the bridge classes here
			return true;
		}
		if (member.getAnnotation(ServerSide.class) != null) {
			return true;
		}
		return false;
	}

	/**
	 * transform a.b.type in constructor.type
	 */
	private String replaceFullNameWithConstructor(String typeName) {
		int pos = typeName.lastIndexOf('.');
		return JavascriptKeywords.CONSTRUCTOR + typeName.substring(pos);
	}

	private JS writeAnnotationValue(WriterVisitor visitor, ExpressionTree expr, GenerationContext context) {
		if (expr instanceof NewArrayTree) {
			// special case for array initializer
			List items = new ArrayList();
			for (ExpressionTree item : ((NewArrayTree) expr).getInitializers()) {
				items.add(visitor.scan(item, context));
			}
			return context.js().array(items);
		}
		return visitor.scan(expr, context);
	}

	private JS getAnnotationForElement(AnnotationTree ann, WriterVisitor visitor, GenerationContext context) {
		// build "annotationType": {arg1, arg2, ...}
		// XXX: should use here type names?
		if (AnnotationHelper.getRetentionType(ann.getAnnotationType()) == RetentionPolicy.SOURCE) {
			return null;
		}
		String annEntryKey = ann.getAnnotationType().toString();
		if (!context.getConfiguration().getAnnotations().contains(annEntryKey)) {
			return null;
		}

		List> annotationArgsDesc = new ArrayList>();
		for (ExpressionTree arg : ann.getArguments()) {
			AssignmentTree assign = (AssignmentTree) arg;
			annotationArgsDesc
					.add(NameValue.of(assign.getVariable().toString(), writeAnnotationValue(visitor, assign.getExpression(), context)));
		}
		return context.js().object(annotationArgsDesc);
	}

	private void addAnnotationsForElement(String name, List> props, WriterVisitor visitor,
			List annotations, GenerationContext context) {
		if (annotations.isEmpty()) {
			return;
		}
		List> annotationsDesc = new ArrayList>();
		for (AnnotationTree ann : annotations) {
			JS annotationArgs = getAnnotationForElement(ann, visitor, context);
			if (annotationArgs != null) {
				// XXX: hack here to quote the type name - to change when using the type names
				String annEntryKey = ann.getAnnotationType().toString();
				annotationsDesc.add(NameValue.of("\"" + annEntryKey + "\"", annotationArgs));
			}
		}
		if (!annotationsDesc.isEmpty()) {
			props.add(NameValue.of(name, context.js().object(annotationsDesc)));
		}
	}

	private void addAnnotationsForMethod(MethodTree method, List> props, WriterVisitor visitor, GenerationContext context) {
		String name = method.getName().toString();
		if ("".equals(name)) {
			name = "_init_";
		}

		addAnnotationsForElement(name, props, visitor, method.getModifiers().getAnnotations(), context);
		for (int i = 0; i < method.getParameters().size(); ++i) {
			addAnnotationsForElement(method.getName().toString() + "$" + i, props, visitor, method.getParameters().get(i).getModifiers()
					.getAnnotations(), context);
		}
	}

	/**
	 * build the annotation description element
	 *
	 * 
	 * $annotations : {
	 * _: {....}
	 * field1: {...}
	 * method1: {...}
	 * method1$0:  {...}
	 * method1$1:  {...}...
	 * }
	 * 
* * for each annotation list you have: * *
	 * {
	 * "annotationType1": [expr1, expr2, expr3],
	 * "annotationType2": []
	 * }
	 * 
*/ private JS getAnnotationDescription(WriterVisitor visitor, ClassTree classTree, GenerationContext context) { List> props = new ArrayList>(); addAnnotationsForElement("_", props, visitor, classTree.getModifiers().getAnnotations(), context); for (Tree member : classTree.getMembers()) { if (MemberWriters.shouldSkip(context.getCurrentWrapper().child(member))) { continue; } if (member instanceof VariableTree) { VariableTree field = (VariableTree) member; addAnnotationsForElement(field.getName().toString(), props, visitor, field.getModifiers().getAnnotations(), context); } else if (member instanceof MethodTree) { addAnnotationsForMethod((MethodTree) member, props, visitor, context); } } return context.js().object(props); } @SuppressWarnings("unused") private boolean generareEnum(WriterVisitor visitor, ClassTree tree, GenerationContext context, List stmts) { Element type = TreeUtils.elementFromDeclaration(tree); if (type.getKind() != ElementKind.ENUM) { return false; } JavaScriptBuilder js = context.js(); // add all anum entries List enumEntries = new ArrayList(); for (Element member : ElementUtils.getAllFieldsIn((TypeElement) type)) { if (member.getKind() == ElementKind.ENUM_CONSTANT) { enumEntries.add(js.string(member.getSimpleName().toString())); } } JS enumConstructor = js.functionCall(js.property(js.name(GeneratorConstants.STJS), "enumeration"), enumEntries); String typeName = context.getNames().getTypeName(context, type, DependencyType.EXTENDS); if (typeName.contains(".")) { // inner class or namespace boolean innerClass = type.getEnclosingElement().getKind() != ElementKind.PACKAGE; String leftSide = innerClass ? replaceFullNameWithConstructor(typeName) : typeName; stmts.add(js.expressionStatement(js.assignment(AssignOperator.ASSIGN, js.name(leftSide), enumConstructor))); } else { // regular class stmts.add(js.variableDeclaration(true, Collections.singleton(NameValue.of(typeName, enumConstructor)))); } return true; } /** * Special generation for classes marked with {@link org.stjs.javascript.annotation.GlobalScope}. The name of the * class must appear nowhere. */ private boolean generateGlobal(WriterVisitor visitor, ClassTree tree, GenerationContext context, List stmts) { if (!context.getCurrentWrapper().isGlobal()) { return false; } // print members List nonConstructors = getAllMembersExceptConstructors(tree); for (Tree member : nonConstructors) { stmts.add(visitor.scan(member, context)); } addStaticInitializers(visitor, tree, context, stmts); addMainMethodCall(tree, stmts, context); return true; } private void addConstructorStatement(WriterVisitor visitor, ClassTree tree, GenerationContext context, List stmts) { boolean anonymousClass = tree.getSimpleName().length() == 0; if (anonymousClass) { // anonymous class - nothing to do the constructor will be added directly return; } JavaScriptBuilder js = context.js(); Element type = TreeUtils.elementFromDeclaration(tree); String typeName = context.getNames().getTypeName(context, type, DependencyType.EXTENDS); if (typeName.contains(".")) { // inner class or namespace // generate [ns.]typeName = function() {...} boolean innerClass = type.getEnclosingElement().getKind() != ElementKind.PACKAGE; String leftSide = innerClass ? replaceFullNameWithConstructor(typeName) : typeName; stmts.add(js.expressionStatement(js.assignment(AssignOperator.ASSIGN, js.name(leftSide), getConstructor(visitor, tree, context)))); } else { // regular class // generate var typeName = function() {...} stmts.add(js.variableDeclaration(true, typeName, getConstructor(visitor, tree, context))); } } @Override public JS visit(WriterVisitor visitor, ClassTree tree, GenerationContext context) { JavaScriptBuilder js = context.js(); List stmts = new ArrayList(); if (generateGlobal(visitor, tree, context, stmts)) { // special construction for globals return js.statements(stmts); } addNamespace(tree, context, stmts); if (generareEnum(visitor, tree, context, stmts)) { // special construction for enums return js.statements(stmts); } Element type = TreeUtils.elementFromDeclaration(tree); String typeName = context.getNames().getTypeName(context, type, DependencyType.EXTENDS); JS name = js.name(typeName); JS superClazz = getSuperClass(tree, context); JS interfaces = getInterfaces(tree, context); JS members = getMembers(visitor, tree, context); JS typeDesc = getTypeDescription(visitor, tree, context); JS annotationDesc = getAnnotationDescription(visitor, tree, context); boolean anonymousClass = tree.getSimpleName().length() == 0; if (anonymousClass) { // anonymous class name = getConstructor(visitor, tree, context); } addConstructorStatement(visitor, tree, context, stmts); @SuppressWarnings("unchecked") JS extendsCall = js.functionCall(js.property(js.name(GeneratorConstants.STJS), "extend"), Arrays.asList(name, superClazz, interfaces, members, typeDesc, annotationDesc)); if (anonymousClass) { stmts.add(extendsCall); } else { stmts.add(context.withPosition(tree, js.expressionStatement(extendsCall))); } addStaticInitializers(visitor, tree, context, stmts); addMainMethodCall(tree, stmts, context); return js.statements(stmts); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy