/* * Copyright (c) 2004-2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.internal.net4j.testrecorder; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.model.CDOModelUtil; import org.eclipse.emf.cdo.common.model.CDOType; import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; import org.eclipse.emf.cdo.common.util.CDOTimeProvider; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.eresource.CDOResourceNode; import org.eclipse.emf.cdo.eresource.EresourcePackage; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager; import org.eclipse.emf.cdo.spi.common.revision.CDOFeatureDeltaVisitorImpl; import org.eclipse.emf.cdo.transaction.CDOMerger; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.transaction.CDOTransactionHandler1; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.net4j.util.StringUtil; import org.eclipse.net4j.util.event.EventUtil; import org.eclipse.net4j.util.io.IOUtil; import org.eclipse.net4j.util.lifecycle.ILifecycle; import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter; import org.eclipse.net4j.util.om.OMPlatform; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EEnumLiteral; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.spi.cdo.CDOSessionProtocol; import org.eclipse.emf.spi.cdo.FSMUtil; import org.eclipse.emf.spi.cdo.InternalCDOView; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * @author Eike Stepper */ @SuppressWarnings("restriction") public final class TestRecorder implements CDOTransactionHandler1.WithUndo { public static final TestRecorder INSTANCE = new TestRecorder(); private static final String OUTPUT_FOLDER = OMPlatform.INSTANCE.getProperty("org.eclipse.emf.cdo.test.recorder.outputFolder"); private static final String CLASS_NAME = OMPlatform.INSTANCE.getProperty("org.eclipse.emf.cdo.test.recorder.className"); private static final String DESCRIPTION = OMPlatform.INSTANCE.getProperty("org.eclipse.emf.cdo.test.recorder.description"); private static final boolean RECORD_VIEWS = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.test.recorder.recordViews"); private static final Map<Object, String> TYPES = new HashMap<Object, String>(); static { TYPES.put("mango", "getMangoFactory()"); TYPES.put("model1", "getModel1Factory()"); TYPES.put("model2", "getModel2Factory()"); TYPES.put("model3", "getModel3Factory()"); TYPES.put("subpackage", "getModel3SubpackageFactory()"); TYPES.put("model4", "getModel4Factory()"); TYPES.put("model5", "getModel5Factory()"); TYPES.put("model6", "getModel6Factory()"); TYPES.put(EresourcePackage.Literals.CDO_RESOURCE_FOLDER, "folder"); TYPES.put(EresourcePackage.Literals.CDO_RESOURCE, "resource"); TYPES.put(EresourcePackage.Literals.CDO_TEXT_RESOURCE, "text"); TYPES.put(EresourcePackage.Literals.CDO_BINARY_RESOURCE, "file"); } private final Map<Object, Variable> variables = new HashMap<Object, Variable>(); private final Map<String, AtomicInteger> typeCounters = new HashMap<String, AtomicInteger>(); private PrintStream out; private TestRecorder() { } private void println(String line) { if (out == null) { String className = CLASS_NAME; String packageName; if (StringUtil.isEmpty(className)) { className = "RecordedTest"; packageName = ""; } else { int lastDot = className.lastIndexOf('.'); if (lastDot != -1) { packageName = className.substring(0, lastDot); className = className.substring(lastDot + 1); } else { packageName = ""; } } if (StringUtil.isEmpty(OUTPUT_FOLDER)) { out = System.out; } else { String prefix; String suffix; int star = className.indexOf('*'); if (star != -1) { prefix = className.substring(0, star); suffix = className.substring(star + 1); } else { prefix = className; suffix = ""; } className = prefix + suffix; File folder = new File(OUTPUT_FOLDER, packageName.replace('.', '/')); File file = new File(folder, className + ".java"); char c = 'a'; while (file.exists()) { className = prefix + Character.toString(c++) + suffix; file = new File(folder, className + ".java"); } try { out = new PrintStream(file); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (out != null) { out.println("\t}"); out.println("}"); IOUtil.close(out); } } }); } catch (FileNotFoundException ex) { ex.printStackTrace(); out = System.out; } } if (packageName.length() != 0) { out.println("package " + packageName + ";"); out.println(); } out.println("import org.eclipse.emf.cdo.common.branch.CDOBranch;"); out.println("import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;"); out.println("import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;"); out.println("import org.eclipse.emf.cdo.eresource.CDOResource;"); out.println("import org.eclipse.emf.cdo.session.CDOSession;"); out.println("import org.eclipse.emf.cdo.tests.AbstractCDOTest;"); out.println("import org.eclipse.emf.cdo.transaction.CDOTransaction;"); out.println("import org.eclipse.emf.spi.cdo.DefaultCDOMerger;"); out.println(); if (!StringUtil.isEmpty(DESCRIPTION)) { out.println("/**"); out.println(" * " + DESCRIPTION); out.println(" */"); } out.println("public class " + className + " extends AbstractCDOTest"); out.println("{"); out.println("\tpublic void testCase() throws Exception"); out.println("\t{"); } out.println("\t\t" + line); } public synchronized void openSession(final TestRecorderSession session) { Variable variable = createVariable("session", session); println("CDOSession " + variable + " = openSession();"); addCloseListener(variable); } public synchronized void createBranch(TestRecorderBranch branch, long originalBaseTimeStamp) { Variable variable = createVariable("branch", branch); String lhs = "CDOBranch " + variable + " = "; String create = ".createBranch(" + list(quot(branch.getName()), formatTimeStamp(originalBaseTimeStamp, true)) + ");"; CDOBranch baseBranch = branch.getBase().getBranch(); if (baseBranch.isMainBranch()) { println(lhs + variables.get(getSession(branch)) + ".getBranchManager().getMainBranch()" + create); } else { Variable baseBranchVariable = variables.get(baseBranch); if (baseBranchVariable != null) { println(lhs + baseBranchVariable + create); } else { println(lhs + variables.get(getSession(branch)) + ".getBranchManager().getBranch(" + quot(baseBranch.getPathName()) + ")" + create); } } } public synchronized void renameBranch(TestRecorderBranch branch) { } public synchronized void openView(final TestRecorderView view, CDOSession session, CDOBranch branch, long timeStamp) { if (RECORD_VIEWS) { Variable variable = createVariable("view", view); println( "CDOView " + variable + " = " + variables.get(session) + ".openView(" + list(formatBranch(branch, true), formatTimeStamp(timeStamp, true)) + ");"); addCloseListener(variable); } } public synchronized void openTransaction(TestRecorderTransaction transaction, CDOSession session, CDOBranch branch) { Variable variable = createVariable("transaction", transaction); println("CDOTransaction " + variable + " = " + variables.get(session) + ".openTransaction(" + formatBranch(branch, true) + ");"); addCloseListener(variable); transaction.addTransactionHandler(this); } public synchronized void attachingObject(CDOTransaction transaction, CDOObject object) { if (object instanceof CDOResourceNode) { CDOResourceNode resourceNode = (CDOResourceNode)object; EClass eClass = resourceNode.eClass(); String typeName = eClass.getName(); String type = TYPES.get(eClass); Variable variable = createVariable(type, resourceNode); println(typeName + " " + variable + " = " + variables.get(transaction) + "." + typeName.replace("CDO", "create") + "(getResourcePath(" + quot(resourceNode.getPath()) + "));"); } } public synchronized void detachingObject(CDOTransaction transaction, CDOObject object) { if (object instanceof CDOResourceNode) { CDOResourceNode resourceNode = (CDOResourceNode)object; println(formatResourceNode(resourceNode) + ".delete(null);"); variables.remove(resourceNode); } } public synchronized void modifyingObject(CDOTransaction transaction, final CDOObject object, CDOFeatureDelta featureDelta) { if (object instanceof CDOResourceNode) { // TODO Handle path modifications. // CDOResourceNode resourceNode = (CDOResourceNode)object; return; } final EStructuralFeature feature = featureDelta.getFeature(); final String featureName = StringUtil.cap(feature.getName()); featureDelta.accept(new CDOFeatureDeltaVisitorImpl() { @Override public void visit(CDOClearFeatureDelta delta) { if (filter(feature)) { return; } Object value = object.eGet(feature); if (value == null || value instanceof List<?> && ((List<?>)value).isEmpty()) { return; } println(formatGet() + ".clear();"); } @Override public void visit(CDOUnsetFeatureDelta delta) { if (filter(feature)) { return; } println(formatGet() + ".unset();"); } @Override public void visit(CDOSetFeatureDelta delta) { if (filter(feature)) { return; } String value = formatValue(object, feature, delta.getValue()); println(formatObject(object) + ".set" + featureName + "(" + value + ");"); } @Override public void visit(CDORemoveFeatureDelta delta) { if (filter(feature)) { return; } println(formatGet() + ".remove(" + delta.getIndex() + ");"); } @Override public void visit(CDOAddFeatureDelta delta) { if (filter(feature)) { return; } List<?> list = (List<?>)object.eGet(feature); Integer index = delta.getIndex() >= list.size() - 1 ? null : delta.getIndex(); String value = formatValue(object, feature, delta.getValue()); println(formatGet() + ".add(" + list(index, value) + ");"); } @Override public void visit(CDOMoveFeatureDelta delta) { println(formatGet() + ".move(" + delta.getOldPosition() + ", " + delta.getNewPosition() + ");"); } private boolean filter(EStructuralFeature feature) { if (feature instanceof EReference) { EReference reference = (EReference)feature; EReference oppositeReference = reference.getEOpposite(); if (oppositeReference != null) { if (reference.isMany()) { if (oppositeReference.isMany()) { if (pick(reference, oppositeReference)) { return true; } } } else { if (oppositeReference.isMany()) { return true; } if (pick(reference, oppositeReference)) { return true; } } } } return false; } private boolean pick(EReference r1, EReference r2) { return getID(r1).compareTo(getID(r2)) > 0; } private String getID(EReference r) { EClass c = r.getEContainingClass(); return c.getEPackage().getNsURI() + "#" + c.getName() + "#" + r.getName(); } private String formatGet() { return formatObject(object) + ".get" + featureName + "()"; } }); } public synchronized void undoingObject(CDOTransaction transaction, CDOObject object, CDOFeatureDelta featureDelta) { modifyingObject(transaction, object, featureDelta); } public synchronized void mergeTransaction(TestRecorderTransaction transaction, CDOBranch source, CDOMerger merger) { println(variables.get(transaction) + ".merge(" + formatBranch(source, false) + ", new " + simpleClassName(merger) + "());"); } public synchronized void mergeTransaction(TestRecorderTransaction transaction, CDOBranchPoint source, CDOMerger merger) { println(variables.get(transaction) + ".merge(" + formatBranchPoint(source, false) + ", new " + simpleClassName(merger) + "());"); } public synchronized void commitTransaction(TestRecorderTransaction transaction, CDOCommitInfo commitInfo) { Variable variable = createVariable("commit", commitInfo); long timeStamp = commitInfo.getTimeStamp(); Variable oldVariable = variables.put(timeStamp, variable); if (oldVariable != null) { variables.put(timeStamp, oldVariable); } CDOBranchPoint branchPoint = CDOBranchUtil.copyBranchPoint(commitInfo); oldVariable = variables.put(branchPoint, variable); if (oldVariable != null) { variables.put(branchPoint, oldVariable); } StringBuilder builder = new StringBuilder("CDOCommitInfo " + variable + " = commitAndSync(" + variables.get(transaction)); for (InternalCDOView view : transaction.getSession().getViews()) { if (view != transaction) { Variable viewVariable = variables.get(view); if (viewVariable != null) { builder.append(", "); builder.append(viewVariable); } } } builder.append(");"); println(builder.toString()); } public synchronized void rollbackTransaction(TestRecorderTransaction transaction) { println(variables.get(transaction) + ".rollback();"); } private Variable createVariable(String type, Object object) { AtomicInteger counter = typeCounters.get(type); if (counter == null) { counter = new AtomicInteger(); typeCounters.put(type, counter); } int number = counter.incrementAndGet(); Variable variable = new Variable(type, number, object); variables.put(object, variable); return variable; } private void addCloseListener(final Variable variable) { final Object object = variable.getObject(); EventUtil.addListener(object, new LifecycleEventAdapter() { @Override protected void onDeactivated(ILifecycle lifecycle) { variables.remove(object); println(variable + ".close();"); } }); } private String formatBranchPoint(CDOBranchPoint branchPoint, boolean optional) { CDOBranch branch = branchPoint.getBranch(); long timeStamp = branchPoint.getTimeStamp(); if (branch.isMainBranch() && timeStamp == CDOBranchPoint.UNSPECIFIED_DATE && optional) { return ""; } Variable branchPointVariable = variables.get(branchPoint); if (branchPointVariable == null) { branchPointVariable = createVariable("point", branchPoint); println("CDOBranchPoint " + branchPointVariable + " = " + formatBranch(branch, false) + ".getPoint(" + formatTimeStamp(timeStamp, false) + ");"); } return branchPointVariable.toString(); } private String formatBranch(CDOBranch branch, boolean optional) { if (branch.isMainBranch() && optional) { return ""; } Variable branchVariable = variables.get(branch); if (branchVariable == null) { Variable sessionVariable = variables.get(getSession(branch)); if (branch.isMainBranch()) { return sessionVariable + ".getBranchManager().getMainBranch()"; } branchVariable = createVariable("branch", branch); println("CDOBranch " + branchVariable + " = " + sessionVariable + ".getBranchManager().getBranch(" + quot(branch.getPathName()) + ");"); } return branchVariable.toString(); } private String formatTimeStamp(long timeStamp, boolean optional) { if (timeStamp == CDOBranchPoint.UNSPECIFIED_DATE) { if (optional) { return ""; } return "CDOBranchPoint.UNSPECIFIED_DATE"; } Variable variable = variables.get(timeStamp); if (variable != null && variable.getObject() instanceof CDOTimeProvider) { return variable + ".getTimeStamp()"; } return timeStamp + "L"; } private String formatResourceNode(CDOResourceNode resourceNode) { Variable variable = variables.get(resourceNode); if (variable == null) { EClass eClass = resourceNode.eClass(); String typeName = eClass.getName(); String type = TYPES.get(eClass); variable = createVariable(type, resourceNode); println(typeName + " " + variable + " = " + variables.get(resourceNode.cdoView()) + "." + typeName.replace("CDO", "get") + "(getResourcePath(" + quot(resourceNode.getPath()) + "));"); } return variable.toString(); } private String formatObject(CDOObject object) { Variable variable = variables.get(object); if (variable == null) { EClass eClass = object.eClass(); String typeName = eClass.getName(); String type = StringUtil.uncap(eClass.getName()); variable = createVariable(type, object); if (FSMUtil.isTransient(object)) { String packageName = eClass.getEPackage().getName(); String factory = TYPES.get(packageName); if (factory == null) { factory = StringUtil.cap(packageName) + "Factory.eINSTANCE"; } println(typeName + " " + variable + " = " + factory + ".create" + eClass.getName() + "();"); for (EStructuralFeature feature : eClass.getEAllStructuralFeatures()) { Object value = object.eGet(feature); if (value instanceof List<?>) { List<?> list = (List<?>)value; for (Object element : list) { println(variable + ".get" + StringUtil.cap(feature.getName()) + "().add(" + formatValue(object, feature, element) + ");"); } } else if (value != null) { println(variable + ".set" + StringUtil.cap(feature.getName()) + "(" + formatValue(object, feature, value) + ");"); } } } else { String rhs; CDOObject container = CDOUtil.getCDOObject(object.eContainer()); if (container != null) { EReference reference = (EReference)object.eContainingFeature(); rhs = formatObject(container) + ".get" + StringUtil.cap(reference.getName()) + "()"; if (reference.isMany()) { List<?> list = (List<?>)container.eGet(reference); int index = list.indexOf(object); rhs += ".get(" + index + ")"; } } else { CDOResource resource = (CDOResource)object.eResource(); int index = resource.getContents().indexOf(object); rhs = "(" + typeName + ")" + formatResourceNode(resource) + ".getContents().get(" + index + ")"; } println(typeName + " " + variable + " = " + rhs + ";"); } } return variable.toString(); } private String formatValue(CDOObject object, EStructuralFeature feature, Object value) { if (value != null) { EClassifier eType = feature.getEType(); if (eType instanceof EEnum) { EEnumLiteral literal = ((EEnum)eType).getEEnumLiteral(value.toString()); return literal.getEEnum().getName() + "." + literal.toString().toUpperCase(); } CDOType type = CDOModelUtil.getType(eType); if (type != null) { value = type.convertToEMF(eType, value); } if (value instanceof String) { return quot((String)value); } if (value instanceof CDOID) { value = object.cdoView().getObject((CDOID)value); } if (value instanceof EObject) { return formatObject(CDOUtil.getCDOObject((EObject)value)); } } return String.valueOf(value); } private static TestRecorderSession getSession(CDOBranch branch) { InternalCDOBranchManager branchManager = (InternalCDOBranchManager)branch.getBranchManager(); return (TestRecorderSession)((CDOSessionProtocol)branchManager.getBranchLoader()).getSession(); } private static String simpleClassName(Object object) { String name = object.getClass().getName(); int lastDot = name.lastIndexOf('.'); if (lastDot != -1) { name = name.substring(lastDot + 1); } return name.replace('$', '.'); } private static String list(Object... values) { StringBuilder builder = new StringBuilder(); for (Object value : values) { if (value != null) { String str = value.toString(); if (str.length() != 0) { if (builder.length() != 0) { builder.append(", "); } builder.append(str); } } } return builder.toString(); } private static String quot(String value) { StringBuilder builder = new StringBuilder(); builder.append("\""); if (value != null && value.length() != 0) { builder.append(value); } builder.append("\""); return builder.toString(); } /** * @author Eike Stepper */ public static final class Variable { private final String type; private final int number; private final Object object; private Variable(String type, int number, Object object) { this.type = type; this.number = number; this.object = object; } public String getType() { return type; } public int getNumber() { return number; } public Object getObject() { return object; } @Override public String toString() { return type + number; } } }