package org.ntlab.deltaViewer; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Point; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.ntlab.deltaExtractor.Alias; import org.ntlab.deltaExtractor.IAliasCollector; import org.ntlab.deltaExtractor.Alias.AliasType; import org.ntlab.deltaViewer.Edge.TypeName; import org.ntlab.trace.ArrayAccess; import org.ntlab.trace.FieldAccess; import org.ntlab.trace.FieldUpdate; import org.ntlab.trace.MethodExecution; import org.ntlab.trace.MethodInvocation; import org.ntlab.trace.Reference; import org.ntlab.trace.Statement; import org.ntlab.trace.TracePoint; import com.mxgraph.model.mxICell; import com.mxgraph.util.mxPoint; import com.mxgraph.view.mxGraphView; public class CollaborationViewer extends MagnetRONViewer { private IObjectCallGraph objectCallGraph; private double scale = 1; public CollaborationViewer() { super(); } /** * Set objectCallGraph and aliasCollector, create vertex object and edge object. * @param objectCallGraph * @param aliasCollector */ public void init(IObjectCallGraph objectCallGraph, IAliasCollector aliasCollector, IObjectLayout layout) { this.objectCallGraph = objectCallGraph; this.aliasCollector = aliasCollector; createObjectVertices(this.objectCallGraph); layout.execute(objectCallGraph, aliasCollector, objectToVertexMap); createEdgeToObject(this.objectCallGraph, this.aliasCollector); } /** * Initialize animation. (再生ボタンを押すとき) */ public void initAnimation() { // reflectCoordinates(mxgraph); // objectVertexにmxGraphの座標を反映させる(ユーザが指定した座標位置を反映) // Fit graph size in visible JFrame. mxGraphView view = mxgraphComponent.getGraph().getView(); int componentWidth = mxgraphComponent.getWidth() - 25; double viewWidth = (double) view.getGraphBounds().getWidth(); // Object component = mxgraph.insertDeltaVertex(mxDefaultParent, "component", "component", "fillColor=white"); //creates a white vertex. // ((mxICell)component).getGeometry().setX(mxgraphComponent.getWidth() - 30); // ((mxICell)component).getGeometry().setY(10); // Object vertex = mxgraph.insertDeltaVertex(mxDefaultParent, "view", "view", "fillColor=white"); //creates a white vertex. // ((mxICell)vertex).getGeometry().setX(view.getGraphBounds().getWidth()); // ((mxICell)vertex).getGeometry().setY(10); // scale = (double)componentWidth/viewWidth; System.out.println(", " + scale); // scale = 1.5; view.setScale(scale); deltaAnimation.setScale(scale); update(); } /** * Step to animation of specified alias. * * @param alias Alias type and occurrence point etc. */ public void stepToAnimation(Alias alias) { try { stepToAnimation(aliasCollector.getAliasList().indexOf(alias)); } catch (IndexOutOfBoundsException e) { stepToAnimation(-1); } } /** * Parent : Step to animation of specified numFrame. * * @param numFrame Current animation frame. */ public void stepToAnimation(int numFrame) { // TODO Implement doLastAnimation to support plural Delta. // TOD curFrame debug. System.out.println("Frame: " + curFrame + "->" + numFrame); // if (numFrame - curFrame == 1) { List<TracePoint> relatedPoints = objectCallGraph.getRelatedPoints(); List<Alias> aliasList = aliasCollector.getAliasList(); Alias curFrameAlias = (0 < numFrame && numFrame <= aliasList.size()) ? aliasList.get(numFrame- 1) : null; Alias numFrameAlias = (0 <= numFrame && numFrame < aliasList.size()) ? aliasList.get(numFrame) : null; if (numFrameAlias != null) { if (curFrameAlias != null) { for (TracePoint rp: relatedPoints) { if (curFrameAlias.getTimeStamp() < rp.getStatement().getTimeStamp() && rp.getStatement().getTimeStamp() < numFrameAlias.getTimeStamp()) { System.out.println("\r\nLast Animation."); doLastAnimation(numFrame, rp); return; } } } doAnimation(curFrame, numFrame); } else if (curFrameAlias != null && numFrameAlias == null) { System.out.println("\r\nLast Animation."); doLastAnimation(numFrame, relatedPoints.get(relatedPoints.size() - 1)); } else { System.out.println("ERROR : Not exist alias."); } // } else { // TODO Considering fast-forwarding animations. // } } private void doLastAnimation(int numFrame, TracePoint relatedPoint) { // TODO Implement doLastAnimation to support plural Delta. outputLog(); curFrame = numFrame; List<Alias> aliasList = aliasCollector.getAliasList(); Alias prevAlias = aliasList.get(numFrame - 1); Alias nextAlias = (numFrame < aliasList.size()) ? aliasList.get(numFrame) : null; // Make ObjectEdge and reset position of vertexObject, remove vertexMethodExecution. Statement statement = relatedPoint.getStatement(); MethodExecution prevMethodExec = prevAlias.getMethodExecution(); if(statement instanceof FieldUpdate) { // Format fieldName. FieldUpdate fieldUpdateStatement = (FieldUpdate) statement; String fieldNames[] = formatFieldName(fieldUpdateStatement.getFieldName()); String fieldName = fieldNames[fieldNames.length-1]; String sourceObjectId = fieldUpdateStatement.getContainerObjId(); createObjectRefrence(fieldUpdateStatement, fieldName); // Change! String targetObjectId = fieldUpdateStatement.getValueObjId(); ObjectVertex targetObjectVertex = objectToVertexMap.get(targetObjectId); if (methodExecToVertexMap.containsKey(prevMethodExec)) { if (methodExecToVertexMap.get(prevMethodExec).getLocals().contains(targetObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getLocals().remove(targetObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeLocal: " + targetObjectVertex.getLabel()); } else if (methodExecToVertexMap.get(prevMethodExec).getArguments().contains(targetObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getArguments().remove(targetObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeArgument: " + targetObjectVertex.getLabel()); } } if (nextAlias == null || (nextAlias != null && !prevMethodExec.getSignature().equals(nextAlias.getMethodSignature()))) { removeCalledMethodExecutionVertex(objectToVertexMap.get(sourceObjectId), prevMethodExec.getCallerMethodExecution(), prevMethodExec); updateObjectVertices(); } } if(statement instanceof MethodInvocation) { MethodInvocation methodInvStatement = (MethodInvocation) statement; MethodExecution calledMethodExec = methodInvStatement.getCalledMethodExecution(); String methodSignature = calledMethodExec.getSignature(); String srcClassName = null; String srcObjId = null; String tgtObjId = null; //ArrayやListのときだけラベルを付ける(確実に分かっているものとき)getSignature->contains("List.get(") || "Map.get(") <ホワイトリスト> // if (methodExec.getSignature().contains("List.add(") || // methodExec.getSignature().contains("Map.put(")) { if (calledMethodExec.isCollectionType() && (methodSignature.contains("add(") || methodSignature.contains("set(") || methodSignature.contains("put(") || methodSignature.contains("push(") || methodSignature.contains("addElement("))) { srcClassName = calledMethodExec.getThisClassName(); srcObjId = calledMethodExec.getThisObjId(); tgtObjId = calledMethodExec.getArguments().get(0).getId(); ObjectVertex tgtObjectVertex = objectToVertexMap.get(tgtObjId); if (!methodExecToVertexMap.containsKey(calledMethodExec)) { createMethodExecutionVertex(calledMethodExec.getThisObjId(), methodInvStatement.getCallerSideMethodName(), calledMethodExec); update(); moveArgumentObjectVertex(calledMethodExec, tgtObjectVertex, methodExecToVertexMap.get(calledMethodExec)); update(); createObjectRefrence(srcClassName, srcObjId, tgtObjId); removeCalledMethodExecutionVertex(null, prevMethodExec, calledMethodExec); } else { createObjectRefrence(srcClassName, srcObjId, tgtObjId); } if (methodExecToVertexMap.containsKey(prevMethodExec)) { if (methodExecToVertexMap.get(prevMethodExec).getLocals().contains(tgtObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getLocals().remove(tgtObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeLocal: " + tgtObjectVertex.getLabel()); } else if (methodExecToVertexMap.get(prevMethodExec).getArguments().contains(tgtObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getArguments().remove(tgtObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeArgument: " + tgtObjectVertex.getLabel()); } } List<MethodExecution> methodExecList = new ArrayList<>(methodExecToVertexMap.keySet()); System.out.println(methodExecList.size()); if (nextAlias == null || (nextAlias != null && !prevMethodExec.getSignature().equals(nextAlias.getMethodSignature()))) { if (methodExecList.size() > 1) { removeCalledMethodExecutionVertex(null, prevMethodExec.getCallerMethodExecution(), prevMethodExec); } else { removeCalledMethodExecutionVertex(null, null, prevMethodExec); } } updateObjectVertices(); } else { // this to another srcClassName = methodInvStatement.getThisClassName(); srcObjId = methodInvStatement.getThisObjId(); tgtObjId = calledMethodExec.getReturnValue().getId(); ObjectVertex tgtObjectVertex = objectToVertexMap.get(tgtObjId); createObjectRefrence(srcClassName, srcObjId, tgtObjId); if (methodExecToVertexMap.containsKey(prevMethodExec)) { if (methodExecToVertexMap.get(prevMethodExec).getLocals().contains(tgtObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getLocals().remove(tgtObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeLocal: " + tgtObjectVertex.getLabel()); } else if (methodExecToVertexMap.get(prevMethodExec).getArguments().contains(tgtObjectVertex)) { methodExecToVertexMap.get(prevMethodExec).getArguments().remove(tgtObjectVertex); System.out.println(methodExecToVertexMap.get(prevMethodExec).getLabel() + " :removeArgument: " + tgtObjectVertex.getLabel()); } } if (nextAlias == null || (nextAlias != null && !calledMethodExec.getSignature().equals(nextAlias.getMethodSignature()))) { removeCalledMethodExecutionVertex(objectToVertexMap.get(srcObjId), prevMethodExec, calledMethodExec); updateObjectVertices(); } } } // MethodExecution tempMethodExec = alias.getMethodExecution(); // //ArrayやListのときだけラベルを付ける(確実に分かっているものとき)getSignature->contains("List.get(") || "Map.get(") <ホワイトリスト> // if (tempMethodExec.getSignature().contains("List.add(") || // tempMethodExec.getSignature().contains("Map.put(")) { // String srcClassName = tempMethodExec.getThisClassName(); // String fieldName = tempMethodExec.getArguments().get(0).getId(); // System.out.println("rTHIS " + srcClassName + ", " + fieldName); // } // Statement tempStatement = alias.getOccurrencePoint().getStatement(); -> MethodInvocation // if(tempStatement instanceof FieldAccess) { // FieldAccess fieldAccessStatement = (FieldAccess) tempStatement; // String fieldNames[] = formatFieldName(fieldAccessStatement.getFieldName()); // String srcClassName = fieldNames[0]; // String fieldName = fieldNames[1]; // String sourceObjectId = fieldAccessStatement.getContainerObjId(); // System.out.println(fieldName); // createObjectRefrence(fieldAccessStatement, fieldName); // removeCalledMethodExecutionVertex(objectToVertexMap.get(sourceObjectId), alias.getMethodExecution().getCallerMethodExecution(), alias.getMethodExecution()); // updateObjectVertices(); // } // Add a vertex to the graph in a transactional fashion. The vertex is actually a 'cell' in jgraphx terminology. mxgraph.getModel().beginUpdate(); try { List<MethodExecution> meList = new ArrayList<>(methodExecToVertexMap.keySet()); Collections.reverse(meList); System.out.println(meList.size()); for(int i = 0; i < meList.size(); i++) { String objectId = meList.get(i).getThisObjId(); ObjectVertex sourceVertexObject = objectToVertexMap.get(objectId); // sourceVertex MethodExecution me = meList.get(i); if (nextAlias != null && me.getSignature().equals(nextAlias.getMethodSignature())) break; if (i != meList.size()-1) { for(Statement st: me.getStatements()) { if(st instanceof MethodInvocation) { MethodExecution calledMethodExec = ((MethodInvocation) st).getCalledMethodExecution(); String calledObjectId = calledMethodExec.getThisObjId(); System.out.println(calledObjectId); if(objectToVertexMap.containsKey(calledObjectId)) { mxICell calledCell = (mxICell)objectToVertexMap.get(calledObjectId).getCell(); Point absolutePointCalledCell = getAbsolutePointforCell(calledCell); System.out.println(objectId + ", " + me.getSignature()); // objectToVertexMap.get(calledObjectId).resetCellPosition(); // if (methodExecToVertexMap.get(methodExec).getArguments().contains(objectToVertexMap.get(calledObjectId)) || methodExecToVertexMap.get(methodExec).getLocals().contains(objectToVertexMap.get(calledObjectId))) { // calledCell.getParent().remove(calledCell); // calledCell.setParent(mxDefaultParent); // calledCell.getGeometry().setX(absolutePointCalledCell.getX()); // calledCell.getGeometry().setY(absolutePointCalledCell.getY()); // deltaAnimation.setVertexAnimation(calledCell, new mxPoint(objectToVertexMap.get(calledObjectId).getInitialX(), objectToVertexMap.get(calledObjectId).getInitialY())); // deltaAnimation.startVertexAnimation(); // } removeCalledMethodExecutionVertex(sourceVertexObject, me.getCallerMethodExecution(), me); updateObjectVertices(); // removeVertexMethodExecution(sourceVertexObject, methodExec); // update(); break; } } } } else { outputLog(); // Change! List<ObjectVertex> arguments = new ArrayList<>(methodExecToVertexMap.get(me).getArguments()); List<ObjectVertex> locals = new ArrayList<>(methodExecToVertexMap.get(me).getLocals()); if (arguments.size() != 0) { for (ObjectVertex vo: arguments) { mxICell cell = (mxICell)vo.getCell(); Point absolutePointCell = getAbsolutePointforCell(cell); cell.getParent().remove(cell); cell.setParent(mxDefaultParent); cell.getGeometry().setX(absolutePointCell.getX()); cell.getGeometry().setY(absolutePointCell.getY()); deltaAnimation.setVertexAnimation(cell, new mxPoint(vo.getInitialX(), vo.getInitialY())); deltaAnimation.startVertexAnimation(); methodExecToVertexMap.get(me).getArguments().remove(vo); } }else if (locals.size() != 0) { for (ObjectVertex vo: locals) { mxICell cell = (mxICell)vo.getCell(); Point absolutePointCell = getAbsolutePointforCell(cell); cell.getParent().remove(cell); cell.setParent(mxDefaultParent); cell.getGeometry().setX(absolutePointCell.getX()); cell.getGeometry().setY(absolutePointCell.getY()); deltaAnimation.setVertexAnimation(cell, new mxPoint(vo.getInitialX(), vo.getInitialY())); deltaAnimation.startVertexAnimation(); methodExecToVertexMap.get(me).getLocals().remove(vo); } } updateObjectVertices(); } } } finally { mxgraph.getModel().endUpdate(); } update(); } /** * Create vertices(mxGraph) and objectVertices in objectToVertexMap. Vertices(mxGraph) coordinate are appropriate. * @param objectCallGraph */ private void createObjectVertices(IObjectCallGraph objectCallGraph) { //Add a vertex to the graph in a transactional fashion. The vertex is actually a 'cell' in jgraphx terminology. mxgraph.getModel().beginUpdate(); try { // Create vertices(mxGraph) and objectVertices. List<Reference> refList = objectCallGraph.getReferences(); int ocgSize = refList.size(); double vertexObjWidth = VERTEX_OBJECT_SIZE.getWidth(); double vertexObjHeight = VERTEX_OBJECT_SIZE.getHeight(); { MethodExecution coordinator = objectCallGraph.getStartPoints().get(0); String coordinatorObjId = coordinator.getThisObjId(); String coordinatorClassName = coordinator.getThisClassName(); Object vertex = mxgraph.insertDeltaVertex(mxDefaultParent, coordinatorObjId, coordinatorClassName, 0, 0, VERTEX_OBJECT_SIZE.getWidth(), VERTEX_OBJECT_SIZE.getHeight(), "fillColor=white"); //creates a white vertex. objectToVertexMap.put(coordinatorObjId, new ObjectVertex(coordinatorClassName, vertex, 0, 0)); } for (int i = 0; i < ocgSize; i++) { Reference ref = refList.get(i); if (!ref.isCreation() && !ref.getSrcObjectId().equals(ref.getDstObjectId())) { // srcSide if (!objectToVertexMap.containsKey(ref.getSrcObjectId())) { System.out.println("srcSide: " + ref.getSrcClassName() + ", " + ref.getSrcObjectId() + ": " + ref.isCreation()); String srcClassName = ref.getSrcClassName(); if (srcClassName.contains("[L")) { srcClassName = formatArrayName(srcClassName); } Object vertex = mxgraph.insertDeltaVertex(mxDefaultParent, ref.getSrcObjectId(), srcClassName, 0, 0, vertexObjWidth, vertexObjHeight, "fillColor=white"); //creates a white vertex. objectToVertexMap.put(ref.getSrcObjectId(), new ObjectVertex(ref.getSrcClassName(), vertex, 0, 0)); } // dstSide if (!objectToVertexMap.containsKey(ref.getDstObjectId())) { System.out.println("dstSide: " + ref.getDstClassName() + ", " + ref.getDstObjectId()); String dstClassName = ref.getDstClassName(); if (dstClassName.contains("[L")) { dstClassName = formatArrayName(dstClassName); } Object vertex = mxgraph.insertDeltaVertex(mxDefaultParent, ref.getDstObjectId(), dstClassName, 0, 0, vertexObjWidth, vertexObjHeight, "fillColor=white"); //creates a white vertex. objectToVertexMap.put(ref.getDstObjectId(), new ObjectVertex(ref.getDstClassName(), vertex, 0, 0)); } } else { if (!objectToVertexMap.containsKey(ref.getSrcObjectId())) { String srcClassName = ref.getSrcClassName(); if (srcClassName.contains("[L")) { srcClassName = formatArrayName(srcClassName); } Object vertex = mxgraph.insertDeltaVertex(mxDefaultParent, ref.getSrcObjectId(), srcClassName, 0, 0, vertexObjWidth, vertexObjHeight, "fillColor=white"); //creates a white vertex. objectToVertexMap.put(ref.getSrcObjectId(), new ObjectVertex(ref.getSrcClassName(), vertex, 0, 0)); } if (!objectToVertexMap.containsKey(ref.getDstObjectId())) { objectToVertexMap.put(ref.getDstObjectId(), new ObjectVertex(ref.getDstClassName(), null, 0, 0)); } else { mxgraph.removeCells(new Object[] {objectToVertexMap.get(ref.getDstObjectId()).getCell()}); objectToVertexMap.get(ref.getDstObjectId()).setCell(null); } } } } finally { mxgraph.getModel().endUpdate(); } } /** * Create MethodExecutionVertex. Be careful to refer objectCallGraph.getStartPoints() when curFrame is 0. * * @param alias */ @Override public void createMethodExecutionVertex(Alias alias) { String objId = alias.getObjectId(); MethodExecution methodExec = alias.getMethodExecution(); String methodExecSignature = methodExec.getSignature(); if (curFrame == 0) { MethodExecution callerMethodExec = methodExec.getCallerMethodExecution(); if (callerMethodExec != null && methodExecSignature != callerMethodExec.getSignature() && objectToVertexMap.containsKey(callerMethodExec.getThisObjId()) && objectCallGraph.getStartPoints().get(0) == callerMethodExec) { createMethodExecutionVertex(callerMethodExec.getThisObjId(), callerMethodExec.getSignature(), callerMethodExec); } createMethodExecutionVertex(objId, methodExecSignature, methodExec); } else if (alias.getObjectId().matches("0") && !methodExecToVertexMap.containsKey(methodExec)) { createMethodExecutionVertex(objId, methodExecSignature, methodExec); } } /** * * @param ocg * @param ac */ private void createEdgeToObject(IObjectCallGraph ocg, IAliasCollector ac) { List<TracePoint> relatedPoints = ocg.getRelatedPoints(); int rpIndex = 0; // relatedPoints index List<Alias> aliasList = ac.getAliasList(); for (int i = 0; i < aliasList.size()-1; i++) { Alias curAlias = aliasList.get(i); Alias nextAlias = aliasList.get(i+1); String curAliasObjId = curAlias.getObjectId(); // srcObjId String nextAliasObjId = nextAlias.getObjectId(); // dstObjId String srcClassName = null; String fieldName = null; // Search for objectReference srcClassName, fieldName between curAlias and nexAlias. if (curAlias.getAliasType() == AliasType.THIS) { if (nextAlias.getAliasType() == AliasType.RETURN_VALUE) { MethodExecution nextMethodExec = nextAlias.getMethodExecution(); //ArrayやListのときだけラベルを付ける(確実に分かっているものとき)getSignature->contains("List.get(") || "Map.get(") <ホワイトリスト> if (nextMethodExec.getSignature().contains("List.get(") || nextMethodExec.getSignature().contains("Map.get(")) { srcClassName = nextMethodExec.getThisClassName(); fieldName = nextMethodExec.getArguments().get(0).getId(); System.out.println("rTHIS " + srcClassName + ", " + nextMethodExec.getArguments().get(0).getId()); } } else { Statement statement = nextAlias.getOccurrencePoint().getStatement(); if(statement instanceof FieldAccess && curAliasObjId.equals(((FieldAccess)statement).getContainerObjId())) { String fieldNames[] = formatFieldName(((FieldAccess)statement).getFieldName()); srcClassName = fieldNames[0]; fieldName = fieldNames[1]; } } System.out.println("THIS " + srcClassName + "(" + curAliasObjId + ") -> " + "(" + nextAliasObjId + "), "+ fieldName); } if(curAlias.getAliasType() == AliasType.ARRAY) { Statement statement= nextAlias.getOccurrencePoint().getStatement(); if(statement instanceof ArrayAccess) { srcClassName = ((ArrayAccess)statement).getArrayClassName(); int index = ((ArrayAccess)statement).getIndex(); fieldName = formatArrayIndex(index); System.out.println("ARRAY " + srcClassName + "(" + curAliasObjId + ") -> " + "(" + nextAliasObjId + "), " + fieldName); } } if (srcClassName != null && fieldName != null && !edgeMap.containsKey(srcClassName + "." + fieldName)) { // Judge AliasList contains relatedPoint. (If contains not to create edge.) if (rpIndex < relatedPoints.size() - 1) { TracePoint rp = relatedPoints.get(rpIndex); Map.Entry<Reference, String> rpInf = getRelatedInformation(rp); if (srcClassName.equals(rpInf.getKey().getSrcClassName()) && fieldName.equals(rpInf.getValue()) && curAliasObjId.equals(rpInf.getKey().getSrcObjectId()) && nextAliasObjId.equals(rpInf.getKey().getDstObjectId())) { rpIndex++; continue; } } // Add a vertex to the graph in a transactional fashion. The vertex is actually a 'cell' in jgraphx terminology. mxgraph.getModel().beginUpdate(); try { Object srcCell = objectToVertexMap.get(curAliasObjId).getCell(); Object dstCell = objectToVertexMap.get(nextAliasObjId).getCell(); if (srcCell != null && dstCell != null) { // isCreation() System.out.println("makeEdgeObject: " + fieldName + ", " + srcClassName + " (" + srcCell.hashCode() + "), " + " (" + dstCell.hashCode() + ")"/* + ", " + dstClassName*/); // BUG:NullPointerException Object edge = mxgraph.insertDeltaEdge(mxDefaultParent, fieldName, fieldName, srcCell, dstCell); Point absPtSrcCell = getAbsolutePointforCell((mxICell)srcCell); Point absPtDstCell = getAbsolutePointforCell((mxICell)dstCell); setEdgePoint((mxICell)edge, absPtSrcCell, absPtDstCell); edgeMap.put(srcClassName + "." + fieldName, new Edge(fieldName, TypeName.Reference, edge)); } } finally { mxgraph.getModel().endUpdate(); } } } } }