Newer
Older
org.ntlab.pushPullRefactoring / src / org / ntlab / pushPullRefactoring / Pull2PushRewriter.java
package org.ntlab.pushPullRefactoring;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;

public class Pull2PushRewriter extends SourceRewriter {

	public Pull2PushRewriter(PushPullDescriptor descriptor) {
		super(descriptor);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void rewrite(IProgressMonitor pm) {
		var srcUnit = descriptor.getSourceClass();
		var dstUnit = descriptor.getDistinationClass();
		var srcDec = new TypeDeclaration[1];
		var messages = searchMethodDeclarations(srcUnit, "Message");
		descriptor.getSourceClass().accept(new ASTVisitor() {
			@Override
			public boolean visit(TypeDeclaration node) {
				srcDec[0] = node;
				return false;
			}
		});
		ASTRewrite srcUnitRewrite = ASTRewrite.create(srcDec[0].getAST());
		String srcValue = annotationValueCase(descriptor.getSourceClassName());
		String dstValue = annotationValueCase(descriptor.getDistinationClassName());

		// @PushReferenceが付与されたフィールドを追加
		String pushrefCode = "@PushReference(\"" + dstValue + "\")" + System.getProperty("line.separator") + "@Pullable(\"direct\")"
				+ System.getProperty("line.separator") + "String " + dstValue + "= " + "\"/" + dstValue + "\";";
		addStatement(srcUnitRewrite, srcUnit, srcDec[0], pushrefCode);

		String importPushRef = "import pushPullRefactor.PushReference;" + System.getProperty("line.separator") 
		+ "import pushPullRefactor.Pullable;" + System.getProperty("line.separator")
		 + System.getProperty("line.separator") ;
		addStatement(srcUnitRewrite, srcUnit, srcDec[0],importPushRef,TypeDeclaration.MODIFIERS2_PROPERTY);
		
		// 転送元クラスのupdateメソッドの最後に,転送先クラスのupdateメソッドを
		//フィールド value を引数として渡して呼び出す行を追加する.
		Type srcType = searchFieldDeclaration(srcUnit, "State").getType();
		for (var update : messages) {
			var invocations = getPutMethodInvocations(update);
			String code = "";
			if (invocations.size() == 0)
				code += "Form ";
			code += "form = new Form();" + System.getProperty("line.separator");
			if (srcType instanceof ParameterizedType) {
				var ptype = (ParameterizedType) srcType;
				if (ptype.getType().toString().equals("List")) {
					var typeArg = (Type) ptype.typeArguments().get(0);
					code += "for (" + typeArg + " i: this.value) {" + System.getProperty("line.separator");
					code += "\t form.param(\"" + srcValue + "\", new ObjectMapper().writeValueAsString(i));"
							+ System.getProperty("line.separator");
					code += "}" + System.getProperty("line.separator");
				}
				if (ptype.getType().toString().equals("Map.Entry")) {
					code += "form.param(\"" + srcValue + "\", new ObjectMapper().writeValueAsString(this.value));"
							+ System.getProperty("line.separator");
				}
			} else {
				code += "form.param(\"" + srcValue + "\", new ObjectMapper().writeValueAsString(this.value));"
						+ System.getProperty("line.separator");
			}

			if (invocations.size() == 0)
				code += "Entity<Form> ";
			code += "entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED);"
					+ System.getProperty("line.separator");
			if (invocations.size() == 0)
				code += "String ";
			code += "result = client.target(\"http://localhost:8080\").path(" + dstValue
					+ ").request().put(entity, String.class);" + System.getProperty("line.separator");
			addStatementLast(srcUnitRewrite, srcUnit, update.getBody(), code, Block.STATEMENTS_PROPERTY);
		}

		var dstDec = new TypeDeclaration[1];
		descriptor.getDistinationClass().accept(new ASTVisitor() {
			@Override
			public boolean visit(TypeDeclaration node) {
				dstDec[0] = node;
				return false;
			}
		});
		ASTRewrite dstUnitRewrite = ASTRewrite.create(dstDec[0].getAST());

		//転送先クラスのPullReferenceが付与されたフィールドを削除する.
		deleteASTNode(dstUnitRewrite, dstUnit, searchFieldDeclaration(dstUnit, "PullReference", srcValue));

		// 転送先クラスに状態を保持するフィールドvalue を追加する.
		Type dstType = searchMethodDeclaration(dstUnit, "Getter").getReturnType2();
		String stateCode = "@State" + System.getProperty("line.separator") + dstType.toString() + " value;";
		addStatement(dstUnitRewrite, dstUnit, dstDec[0], stateCode);
		
		// 転送先クラスにupdateメソッドを追加する.
		// 転送先クラスのgetメソッド内で行っていた,戻り値を計算する処理を
		// updateメソッドに移動し,更新後の値をvalueに保存するようにする.
		String updateCode = generatePutMethod(dstType, new Type[] { srcType }, new String[] { srcValue });
		addStatement(dstUnitRewrite, dstUnit, dstDec[0], updateCode);
		
		// Shipping クラスの getValue() メソッドを value フィールドの値を返すように書き換える.
		Statement statement = (Statement) dstUnitRewrite.createStringPlaceholder("{ return this.value; }",
				ASTNode.VARIABLE_DECLARATION_STATEMENT);
		replaceASTNode(dstUnitRewrite, dstUnit, searchMethodDeclaration(dstUnit, "Getter").getBody(), statement);

		//変更を適用する
		try {
			applyRewrite(srcUnit, srcUnitRewrite, pm);
			applyRewrite(dstUnit, dstUnitRewrite, pm);

		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

	}
	/**
	 * PUTメソッドを作成する.
	 * @param 転送先リソースの型
	 * @param 転送元リソースの型
	 * @param 転送元のリソース名
	 * @return
	 */
	public String generatePutMethod(Type updatedValueType, Type[] paramTypes, String[] srcNames) {
		String paramType = "String";
		
		if (paramTypes[0] instanceof ParameterizedType) {
			//転送元の型がリストだった場合, PUTメソッドが受けとるパラメーターの型をList<String>にする
			var ptype = (ParameterizedType) paramTypes[0];
			if (ptype.getType().toString().equals("List")) {
				paramType = "List<String>";
			}
		}
		
		var args = "@FormParam(\"" + srcNames[0] + "\") "+paramType+" " + srcNames[0] + "_json";
		String srcs = Arrays.stream(srcNames).map(x -> "\"" + x + "\"").collect(Collectors.joining(", "));
		String result = "@PUT" + System.getProperty("line.separator") + "@Message({" + srcs + "})"
				+ System.getProperty("line.separator") + "public void update" + "(" + args
				+ ") throws JsonProcessingException {" + System.getProperty("line.separator");

		// 型宣言
		for (int i = 0; i < paramTypes.length; i++) {
			String initializer = "";	
			if (paramTypes[i] instanceof ParameterizedType) {
				var ptype = (ParameterizedType) paramTypes[i];
				if (ptype.getType().toString().equals("List")) {
					initializer = " = new ArrayList<>()";
				}
			}
			result += paramTypes[i].toString() + " " + srcNames[i] + initializer + ";" + System.getProperty("line.separator");
			
		}
		for (int i = 0; i < paramTypes.length; i++) {
			result += "{" + System.getProperty("line.separator") + "\t";
			if (paramTypes[i] instanceof ParameterizedType) {
				var ptype = (ParameterizedType) paramTypes[i];
				if (ptype.getType().toString().equals("List")) {
					var argType = (Type)ptype.typeArguments().get(0);
					String initializer = "";
					if(argType instanceof ParameterizedType) {
						if (((ParameterizedType)argType).getType().toString().equals("Map.Entry")) {
							initializer = " new ObjectMapper().readValue(str, HashMap.class);";
						}
								//((ParameterizedType)argType).getType();
					}else {
						if(argType.toString().equals("Integer")) {
							initializer = " Integer.parseInt(str);";
						}
					}
					result += "for(String str: "+srcNames[i]+"_json ){ " + System.getProperty("line.separator"); 
					result += convertType(argType) +" i =" + initializer + System.getProperty("line.separator"); 
					result += srcNames[i].toString()+".add(";
					// String value, String tmpValue, Type type
					result += geterateParameterizedTypeStatements("i", "i", argType);
					result += ");" + System.getProperty("line.separator");
					result += "}" + System.getProperty("line.separator");
				}
				if (ptype.getType().toString().equals("Map.Entry")) {
					result += convertType(ptype) + " i = new ObjectMapper().readValue(" + srcNames[i]
							+ "_json, HashMap.class);" + System.getProperty("line.separator") + "\t";
					var mapValue = "i";
					var typeArg =  ptype.typeArguments().get(1);
					if(typeArg instanceof ParameterizedType) {
						if(( (ParameterizedType) ptype.typeArguments().get(1)).getType().toString().equals("Map.Entry")){
							mapValue += ".entrySet().iterator().next().getValue()";
						}
					}
					
					result += srcNames[i] + " = new AbstractMap.SimpleEntry<>(i.entrySet().iterator().next().getKey(), "
							+ geterateParameterizedTypeStatements("i", mapValue, (Type) ptype.typeArguments().get(1)) + ");"
							+ System.getProperty("line.separator");
				}
			}

			result += "}" + System.getProperty("line.separator");

		}

		{
			var returnStatement = new ReturnStatement[1];
			searchMethodDeclaration(descriptor.getDistinationClass(), "Getter").accept(new ASTVisitor() {
				@Override
				public boolean visit(ReturnStatement node) {
					returnStatement[0] = node;
					return super.visit(node);
				}
			});
			;
			result += "this.value = " + returnStatement[0].getExpression() + ";" + System.getProperty("line.separator");
		}
		result += "}" + System.getProperty("line.separator");

		return result;
	}


}