示例#1
0
    @Override
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {

      // ### We should indicate the current thread independently of the
      // ### selection, e.g., with an icon, because the user may change
      // ### the selection graphically without affecting the current
      // ### thread.

      super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
      if (value == null) {
        this.setText("<unavailable>");
      } else {
        StackFrame frame = (StackFrame) value;
        Location loc = frame.location();
        Method meth = loc.method();
        String methName = meth.declaringType().name() + '.' + meth.name();
        String position = "";
        if (meth.isNative()) {
          position = " (native method)";
        } else if (loc.lineNumber() != -1) {
          position = ":" + loc.lineNumber();
        } else {
          long pc = loc.codeIndex();
          if (pc != -1) {
            position = ", pc = " + pc;
          }
        }
        // Indices are presented to the user starting from 1, not 0.
        this.setText("[" + (index + 1) + "] " + methName + position);
      }
      return this;
    }
  @Override
  public String getEventMessage(LocatableEvent event) {
    final Location location = event.location();
    String sourceName;
    try {
      sourceName = location.sourceName();
    } catch (AbsentInformationException e) {
      sourceName = getFileName();
    }

    final boolean printFullTrace = Registry.is("debugger.breakpoint.message.full.trace");

    StringBuilder builder = new StringBuilder();
    if (printFullTrace) {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached.full.trace",
              DebuggerUtilsEx.getLocationMethodQName(location)));
      try {
        final List<StackFrame> frames = event.thread().frames();
        renderTrace(frames, builder);
      } catch (IncompatibleThreadStateException e) {
        builder.append("Stacktrace not available: ").append(e.getMessage());
      }
    } else {
      builder.append(
          DebuggerBundle.message(
              "status.line.breakpoint.reached",
              DebuggerUtilsEx.getLocationMethodQName(location),
              sourceName,
              getLineIndex() + 1));
    }
    return builder.toString();
  }
 /** if this line can not be current => stepOver & return true. return false on the other hand. */
 boolean resolveCanBeCurrent(ThreadReference tr) {
   try {
     Location l = tr.frame(0).location();
     if (l == null) return false;
     return resolveCanBeCurrent(
         tr, Utils.getLineForSource(l.declaringType().name(), l.sourceName(), l.lineNumber()));
   } catch (Exception e) {
   }
   return false;
 }
示例#4
0
文件: Queen.java 项目: rantion/Chess
 public boolean isLegalMove(Location firstLocation, Location secondLocation) {
   boolean validMove = false;
   populateValidMoves(firstLocation);
   for (Location location : validMoves) {
     if (location.getLocation().equals(secondLocation.getLocation())) {
       validMove = true;
     }
   }
   return validMove;
 }
  @Nullable
  private static List<String> getTasksTarget(Location location) {
    if (location instanceof GradleTaskLocation) {
      return ((GradleTaskLocation) location).getTasks();
    }

    PsiElement parent = location.getPsiElement();
    while (parent.getParent() != null && !(parent.getParent() instanceof PsiFile)) {
      parent = parent.getParent();
    }

    if (isCreateTaskMethod(parent)) {
      final GrExpression[] arguments = ((GrMethodCallExpression) parent).getExpressionArguments();
      if (arguments.length > 0
          && arguments[0] instanceof GrLiteral
          && ((GrLiteral) arguments[0]).getValue() instanceof String) {
        return Collections.singletonList((String) ((GrLiteral) arguments[0]).getValue());
      }
    } else if (parent instanceof GrApplicationStatement) {
      PsiElement shiftExpression = parent.getChildren()[1].getChildren()[0];
      if (shiftExpression instanceof GrShiftExpressionImpl) {
        PsiElement shiftiesChild = shiftExpression.getChildren()[0];
        if (shiftiesChild instanceof GrReferenceExpression) {
          return Collections.singletonList(shiftiesChild.getText());
        } else if (shiftiesChild instanceof GrMethodCallExpression) {
          return Collections.singletonList(shiftiesChild.getChildren()[0].getText());
        }
      } else if (shiftExpression instanceof GrMethodCallExpression) {
        return Collections.singletonList(shiftExpression.getChildren()[0].getText());
      }
    }

    return null;
  }
示例#6
0
 // Walk to the specified location, and set the nextState once you get there
 private void walk(Location loc, State nextState) {
   if (getFarmingArea(loc).contains(getMyPlayer().getLocation())) {
     currentLocation = loc;
     currentState = nextState; // This is the last spot of each run. Break here.
   } else {
     checkEnergy();
     status = "Walking to " + loc.toString() + " herb patch";
     step(pathList);
   }
 }
示例#7
0
 // Teleport to the specified location
 private void teleport(Location loc) {
   status = "Teleporting to " + loc.toString();
   if (magic.castSpell(getSpell(loc))) {
     sleep(random(9000, 11000));
     if (getTeleArea(loc)
         .contains(
             getMyPlayer().getLocation())) { // Teleport successful if player is in the tele area
       currentState = State.WALKING;
       pathList = generatePath(getPath(loc));
     }
   }
 }
 protected boolean acceptLocation(
     final DebugProcessImpl debugProcess, ReferenceType classType, final Location loc) {
   Method method = loc.method();
   // Some frameworks may create synthetic methods with lines mapped to user code, see IDEA-143852
   // if (DebuggerUtils.isSynthetic(method)) { return false; }
   if (isAnonymousClass(classType)) {
     if ((method.isConstructor() && loc.codeIndex() == 0) || method.isBridge()) return false;
   }
   return ApplicationManager.getApplication()
       .runReadAction(
           new Computable<Boolean>() {
             @Override
             public Boolean compute() {
               SourcePosition position = debugProcess.getPositionManager().getSourcePosition(loc);
               if (position == null) return false;
               JavaLineBreakpointType type = getXBreakpointType();
               if (type == null) return true;
               return type.matchesPosition(LineBreakpoint.this, position);
             }
           });
 }
示例#9
0
  /**
   * Returns the location, in pixels, of the centre of a tile at a given location, taking the walls
   * into account.
   */
  private Location<Float> centreForTileAtLocation(
      Location<Integer> location, Board board, double startX, double startY, double tileSize) {
    double tileCentreX = tileSize / 2.0;
    double tileCentreY = tileSize / 2.0;

    if (board.hasWallBetween(location, location.locationInDirection(Direction.Up))) {
      tileCentreY += WallWidth / 4.0;
    }

    if (board.hasWallBetween(location, location.locationInDirection(Direction.Down))) {
      tileCentreY -= WallWidth / 4.0;
    }

    if (board.hasWallBetween(location, location.locationInDirection(Direction.Left))) {
      tileCentreX += WallWidth / 4.0;
    }
    if (board.hasWallBetween(location, location.locationInDirection(Direction.Right))) {
      tileCentreX -= WallWidth / 4.0;
    }

    return new Location<>(
        (float) (startX + tileSize * location.x + tileCentreX),
        (float) (startY + tileSize * location.y + tileCentreY));
  }
  /** Executes breakpoint hit event. */
  public void exec(com.sun.jdi.event.Event ev) {
    // S ystem.out.println ("exec "); // NOI18N
    removeStepRequest();
    StepEvent event = (StepEvent) ev;
    ThreadReference tr = event.thread();
    Location loc = event.location();
    int ln = -1;
    String methodName = "?"; // NOI18N
    String className = "?"; // NOI18N
    String lineNumber = "?"; // NOI18N
    String threadName = tr.name();
    Line l = null;

    if (loc != null) {
      if (loc.method() != null) methodName = loc.method().name();
      className = loc.declaringType().name();
      ln = loc.lineNumber();
      if (ln >= 0) lineNumber = "" + loc.lineNumber();
    }

    if (ln != -1)
      try {
        l = Utils.getLineForSource(className, loc.sourceName(), ln);
      } catch (AbsentInformationException e) {
        l = Utils.getLine(className, ln);
      }

    if (resolveCanBeCurrent(tr, l))
      // if this line can not be current resolveCanBeCurrent () calls stepOver
      return;
    // line can be current

    if ((l == null) && (getLastAction() == ACTION_TRACE_INTO))
      // try to find another "intelligent" line of code
      traceToSourceCode(tr);
    // you know - intelligent means that one with source code
    else {
      makeCurrent(threadName, className, methodName, lineNumber, l != null, tr);
      operator.stopRequest();
    }
  }
 @Override
 protected void createRequestForPreparedClass(
     final DebugProcessImpl debugProcess, final ReferenceType classType) {
   if (!isInScopeOf(debugProcess, classType.name())) {
     if (LOG.isDebugEnabled()) {
       LOG.debug(
           classType.name()
               + " is out of debug-process scope, breakpoint request won't be created for line "
               + getLineIndex());
     }
     return;
   }
   try {
     List<Location> locations =
         debugProcess.getPositionManager().locationsOfLine(classType, getSourcePosition());
     if (!locations.isEmpty()) {
       for (Location loc : locations) {
         if (LOG.isDebugEnabled()) {
           LOG.debug(
               "Found location [codeIndex="
                   + loc.codeIndex()
                   + "] for reference type "
                   + classType.name()
                   + " at line "
                   + getLineIndex()
                   + "; isObsolete: "
                   + (debugProcess.getVirtualMachineProxy().versionHigher("1.4")
                       && loc.method().isObsolete()));
         }
         if (!acceptLocation(debugProcess, classType, loc)) {
           continue;
         }
         final BreakpointRequest request =
             debugProcess.getRequestsManager().createBreakpointRequest(this, loc);
         debugProcess.getRequestsManager().enableRequest(request);
         if (LOG.isDebugEnabled()) {
           LOG.debug(
               "Created breakpoint request for reference type "
                   + classType.name()
                   + " at line "
                   + getLineIndex()
                   + "; codeIndex="
                   + loc.codeIndex());
         }
       }
     } else {
       // there's no executable code in this class
       debugProcess
           .getRequestsManager()
           .setInvalid(
               this,
               DebuggerBundle.message(
                   "error.invalid.breakpoint.no.executable.code",
                   (getLineIndex() + 1),
                   classType.name()));
       if (LOG.isDebugEnabled()) {
         LOG.debug(
             "No locations of type " + classType.name() + " found at line " + getLineIndex());
       }
     }
   } catch (ClassNotPreparedException ex) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("ClassNotPreparedException: " + ex.getMessage());
     }
     // there's a chance to add a breakpoint when the class is prepared
   } catch (ObjectCollectedException ex) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("ObjectCollectedException: " + ex.getMessage());
     }
     // there's a chance to add a breakpoint when the class is prepared
   } catch (InvalidLineNumberException ex) {
     if (LOG.isDebugEnabled()) {
       LOG.debug("InvalidLineNumberException: " + ex.getMessage());
     }
     debugProcess
         .getRequestsManager()
         .setInvalid(this, DebuggerBundle.message("error.invalid.breakpoint.bad.line.number"));
   } catch (InternalException ex) {
     LOG.info(ex);
   } catch (Exception ex) {
     LOG.info(ex);
   }
   updateUI();
 }
示例#12
0
  @SuppressWarnings("SuspiciousNameCombination")
  // so we don't get warned about using 'WallWidth' in conjunction with height
  @Override
  protected void paintComponent(Graphics g) {

    double width = this.getWidth();
    double height = this.getHeight();

    Graphics2D graphics2D = (Graphics2D) g;

    graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    graphics2D.setRenderingHint(
        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

    Board board = _gameState.board;

    double ratio = (double) board.width / board.height;

    width = Math.min(width, (height * ratio));
    height = Math.min(height, (width / ratio));

    double startX = this.getWidth() / 2 - width / 2;
    double startY = this.getHeight() / 2 - height / 2;

    g.setColor(Color.yellow);
    g.fillRect(round(startX), round(startY), round(width), round(height));

    // Draw the grid.

    g.setColor(Color.black);

    double step = width / board.width;

    for (double x = startX + step; x < startX + width; x += step) {
      g.drawLine(round(x), round(startY), round(x), round(startY + height));
    }
    for (double y = startY + step; y < startY + height; y += step) {
      g.drawLine(round(startX), round(y), round(startX + width), round(y));
    }

    // Draw the room tiles.

    int x = 0;
    for (Board.Tile[] column : board.tiles) {
      int y = 0;
      for (Board.Tile tile : column) {
        boolean hasRoom = tile.room.isPresent();
        boolean isUnaccessibleSpace = tile.connectedLocations.isEmpty();
        if (hasRoom || isUnaccessibleSpace) {
          g.setColor(hasRoom ? Color.lightGray : Color.cyan);
          g.fillRect(
              round(x * step + startX - 0.5),
              round(y * step + startY - 0.5),
              round(step + 1),
              round(step + 1));

          if (tile.isPassageway(board)) {
            g.setColor(Color.darkGray);
            for (int i = 0; i < step; i += 2) {
              g.fillRect(
                  round(x * step + startX - 0.5),
                  round(y * step + startY - 0.5 + i),
                  round(step + 1),
                  1);
            }
          }
        }

        y++;
      }

      x++;
    }

    // Draw the walls.
    // Loop again to draw over what we already have.

    g.setColor(Color.black);
    x = 0;
    for (Board.Tile[] column : board.tiles) {
      int y = 0;
      for (Board.Tile tile : column) {
        Location<Integer> location = new Location<>(x, y);

        if (board.hasWallBetween(location, new Location<>(x + 1, y))) {
          g.fillRect(
              round(startX + step * (x + 1) - WallWidth / 2),
              round(startY + step * y - 0.5),
              WallWidth,
              round(step + 1));
        }

        if (board.hasWallBetween(location, new Location<>(x, y + 1))) {
          g.fillRect(
              round(startX + step * x - 0.5),
              round(startY + step * (y + 1) - WallWidth / 2),
              round(step + 1),
              WallWidth);
        }

        y++;
      }

      x++;
    }

    // Draw the outer walls.

    g.fillRect(round(startX), round(startY), WallWidth, round(height));
    g.fillRect(round(startX), round(startY), round(width), WallWidth);
    g.fillRect(round(startX + width - WallWidth), round(startY), WallWidth, round(height));
    g.fillRect(round(startX), round(startY + height - WallWidth), round(width), WallWidth);

    // Draw the names for the rooms.

    g.setFont(GameFont);
    g.setColor(Color.black);
    FontMetrics fontMetrics = g.getFontMetrics(GameFont);

    for (Room room : Room.values()) {
      Location<Float> centre = board.centreLocationForRoom(room);
      double centreX = (centre.x + 0.5f) * step + startX;
      double centreY = (centre.y + 0.5f) * step + startY;

      String name = room.shortName().toUpperCase();

      Rectangle2D bounds = fontMetrics.getStringBounds(name, g);

      g.drawString(
          name, (int) (centreX - bounds.getCenterX()), (int) (centreY - bounds.getCenterY()));

      double stringBottom = bounds.getCenterY() + centreY;

      this.drawWeaponTokenForRoom(g, board, room, centreX, stringBottom, step);
    }

    // Draw the players.

    boolean shouldPlayMoveSequence = this.shouldPlayMoveSequence();
    for (Player player : _gameState.allPlayers) {
      boolean drawTransparent = false;
      Location<Float> playerLocation;
      if (shouldPlayMoveSequence
          && // We haven't finished animating the move
          player.character == _lastPlayerMoveCharacter) { // and the move is for this character
        // then we need to lerp between two values in the move sequence for the character's
        // position.

        int lowIndex = (int) Math.floor(_moveSequencePosition);
        int highIndex = (int) Math.ceil(_moveSequencePosition);
        double lerpValue = _moveSequencePosition - lowIndex;

        Location<Float> startLocation =
            this.centreForTileAtLocation(
                _lastPlayerMove.locations[lowIndex], board, startX, startY, step);
        Location<Float> endLocation =
            this.centreForTileAtLocation(
                _lastPlayerMove.locations[highIndex], board, startX, startY, step);
        if (Location.distance(startLocation, endLocation)
            > step * 1.5) { // if the tiles aren't adjacent, allowing for some error.
          drawTransparent = true;
        }
        playerLocation = Location.lerp(startLocation, endLocation, (float) lerpValue);
      } else {
        playerLocation =
            this.centreForTileAtLocation(player.location(), board, startX, startY, step);
      }

      this.drawPlayer(g, playerLocation, player.character, step, drawTransparent);
    }

    // If we're supposed to draw the overlay for the paths, do so. Don't draw the overlay while
    // we're animating.
    if (_accessibleTilePaths != null && !shouldPlayMoveSequence) {
      this.drawAccessibleTilesOverlay(g, _accessibleTilePaths, startX, startY, step);
    }
  }
  // copied from FrameVariablesTree
  private void buildVariables(
      DebuggerContextImpl debuggerContext,
      final EvaluationContextImpl evaluationContext,
      @NotNull DebugProcessImpl debugProcess,
      XValueChildrenList children,
      ObjectReference thisObjectReference,
      Location location)
      throws EvaluateException {
    final Set<String> visibleLocals = new HashSet<String>();
    if (NodeRendererSettings.getInstance().getClassRenderer().SHOW_VAL_FIELDS_AS_LOCAL_VARIABLES) {
      if (thisObjectReference != null
          && debugProcess.getVirtualMachineProxy().canGetSyntheticAttribute()) {
        final ReferenceType thisRefType = thisObjectReference.referenceType();
        if (thisRefType instanceof ClassType
            && location != null
            && thisRefType.equals(location.declaringType())
            && thisRefType.name().contains("$")) { // makes sense for nested classes only
          for (Field field : thisRefType.fields()) {
            if (DebuggerUtils.isSynthetic(field)
                && StringUtil.startsWith(
                    field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) {
              final FieldDescriptorImpl fieldDescriptor =
                  myNodeManager.getFieldDescriptor(myDescriptor, thisObjectReference, field);
              children.add(JavaValue.create(fieldDescriptor, evaluationContext, myNodeManager));
              visibleLocals.add(fieldDescriptor.calcValueName());
            }
          }
        }
      }
    }

    boolean myAutoWatchMode = DebuggerSettings.getInstance().AUTO_VARIABLES_MODE;
    if (evaluationContext == null) {
      return;
    }
    final SourcePosition sourcePosition = debuggerContext.getSourcePosition();
    if (sourcePosition == null) {
      return;
    }

    try {
      if (!XDebuggerSettingsManager.getInstance().getDataViewSettings().isAutoExpressions()
          && !myAutoWatchMode) {
        // optimization
        superBuildVariables(evaluationContext, children);
      } else {
        final Map<String, LocalVariableProxyImpl> visibleVariables =
            getVisibleVariables(getStackFrameProxy());
        final Pair<Set<String>, Set<TextWithImports>> usedVars =
            ApplicationManager.getApplication()
                .runReadAction(
                    new Computable<Pair<Set<String>, Set<TextWithImports>>>() {
                      @Override
                      public Pair<Set<String>, Set<TextWithImports>> compute() {
                        return findReferencedVars(
                            ContainerUtil.union(visibleVariables.keySet(), visibleLocals),
                            sourcePosition);
                      }
                    });
        // add locals
        if (myAutoWatchMode) {
          for (String var : usedVars.first) {
            LocalVariableProxyImpl local = visibleVariables.get(var);
            if (local != null) {
              children.add(
                  JavaValue.create(
                      myNodeManager.getLocalVariableDescriptor(null, local),
                      evaluationContext,
                      myNodeManager));
            }
          }
        } else {
          superBuildVariables(evaluationContext, children);
        }
        final EvaluationContextImpl evalContextCopy =
            evaluationContext.createEvaluationContext(evaluationContext.getThisObject());
        evalContextCopy.setAutoLoadClasses(false);

        final Set<TextWithImports> extraVars =
            computeExtraVars(usedVars, sourcePosition, evaluationContext);

        // add extra vars
        addToChildrenFrom(extraVars, children, evaluationContext);

        // add expressions
        addToChildrenFrom(usedVars.second, children, evalContextCopy);
      }
    } catch (EvaluateException e) {
      if (e.getCause() instanceof AbsentInformationException) {
        children.add(
            new DummyMessageValueNode(
                MessageDescriptor.LOCAL_VARIABLES_INFO_UNAVAILABLE.getLabel(),
                XDebuggerUIConstants.INFORMATION_MESSAGE_ICON));
        // trying to collect values from variable slots
        try {
          for (Map.Entry<DecompiledLocalVariable, Value> entry :
              LocalVariablesUtil.fetchValues(getStackFrameProxy(), debugProcess).entrySet()) {
            children.add(
                JavaValue.create(
                    myNodeManager.getArgumentValueDescriptor(
                        null, entry.getKey(), entry.getValue()),
                    evaluationContext,
                    myNodeManager));
          }
        } catch (Exception ex) {
          LOG.info(ex);
        }
      } else {
        throw e;
      }
    }
  }
  // copied from DebuggerTree
  private void buildVariablesThreadAction(
      DebuggerContextImpl debuggerContext, XValueChildrenList children, XCompositeNode node) {
    try {
      final EvaluationContextImpl evaluationContext = debuggerContext.createEvaluationContext();
      if (evaluationContext == null) {
        return;
      }
      if (!debuggerContext.isEvaluationPossible()) {
        node.setErrorMessage(MessageDescriptor.EVALUATION_NOT_POSSIBLE.getLabel());
        // myChildren.add(myNodeManager.createNode(MessageDescriptor.EVALUATION_NOT_POSSIBLE,
        // evaluationContext));
      }

      final Location location = myDescriptor.getLocation();

      final ObjectReference thisObjectReference = myDescriptor.getThisObject();
      if (thisObjectReference != null) {
        ValueDescriptorImpl thisDescriptor =
            myNodeManager.getThisDescriptor(null, thisObjectReference);
        children.add(JavaValue.create(thisDescriptor, evaluationContext, myNodeManager));
      } else if (location != null) {
        StaticDescriptorImpl staticDecriptor =
            myNodeManager.getStaticDescriptor(myDescriptor, location.declaringType());
        if (staticDecriptor.isExpandable()) {
          children.addTopGroup(
              new JavaStaticGroup(staticDecriptor, evaluationContext, myNodeManager));
        }
      }

      DebugProcessImpl debugProcess = debuggerContext.getDebugProcess();
      if (debugProcess == null) {
        return;
      }

      // add last method return value if any
      final Pair<Method, Value> methodValuePair = debugProcess.getLastExecutedMethod();
      if (methodValuePair != null) {
        ValueDescriptorImpl returnValueDescriptor =
            myNodeManager.getMethodReturnValueDescriptor(
                myDescriptor, methodValuePair.getFirst(), methodValuePair.getSecond());
        children.add(JavaValue.create(returnValueDescriptor, evaluationContext, myNodeManager));
      }
      // add context exceptions
      for (Pair<Breakpoint, Event> pair :
          DebuggerUtilsEx.getEventDescriptors(debuggerContext.getSuspendContext())) {
        final Event debugEvent = pair.getSecond();
        if (debugEvent instanceof ExceptionEvent) {
          final ObjectReference exception = ((ExceptionEvent) debugEvent).exception();
          if (exception != null) {
            final ValueDescriptorImpl exceptionDescriptor =
                myNodeManager.getThrownExceptionObjectDescriptor(myDescriptor, exception);
            children.add(JavaValue.create(exceptionDescriptor, evaluationContext, myNodeManager));
          }
        }
      }

      try {
        buildVariables(
            debuggerContext,
            evaluationContext,
            debugProcess,
            children,
            thisObjectReference,
            location);
        // if (classRenderer.SORT_ASCENDING) {
        //  Collections.sort(myChildren, NodeManagerImpl.getNodeComparator());
        // }
      } catch (EvaluateException e) {
        node.setErrorMessage(e.getMessage());
        // myChildren.add(myNodeManager.createMessageNode(new MessageDescriptor(e.getMessage())));
      }
    } catch (InvalidStackFrameException e) {
      LOG.info(e);
      // myChildren.clear();
      // notifyCancelled();
    } catch (InternalException e) {
      if (e.errorCode() == 35) {
        node.setErrorMessage(DebuggerBundle.message("error.corrupt.debug.info", e.getMessage()));
        // myChildren.add(
        //  myNodeManager.createMessageNode(new
        // MessageDescriptor(DebuggerBundle.message("error.corrupt.debug.info", e.getMessage()))));
      } else {
        throw e;
      }
    }
  }