/** * Parse a single line order. * * <p>Null arguments are not permitted, except for phaseType. If phaseType is * Phase.PhaseType.RETREAT, "Move" format orders will be made into Retreat orders, and convoyed * moves will be disallowed. */ public NJudgeOrder parse( final dip.world.Map map, final OrderFactory orderFactory, final Phase.PhaseType phaseType, final String line) throws OrderException { // create order parsing context final ParseContext pc = new ParseContext(map, orderFactory, phaseType, line); // parse results. This also removes the trailing '.' from the order final ArrayList resultList = new ArrayList(5); final String newOrderLine = removeTrailingDot(parseResults(pc, resultList)); if (Phase.PhaseType.ADJUSTMENT.equals(phaseType)) { return parseAdjustmentOrder(pc, newOrderLine); } else { // tokenize order final String[] tokens = tokenize(newOrderLine); // parse order prefix OrderPrefix prefix = new OrderPrefix(pc, tokens); // parse predicate Orderable order = parsePredicate(pc, prefix, tokens); // parse text results into real results List results = createResults(pc, order, resultList); // create NJudgeOrder return new NJudgeOrder(order, results, pc.isAdjustmentPhase()); } } // parse()
private Orderable parseMove(ParseContext pc, OrderPrefix op, final String[] tokens) throws OrderException { /* 3-StP: Army St Petersburg -> Moscow. (*bounce*) 1-Smy: Army Constantinople -> Aegean Sea -> Greece. keep parsing Locations until END or a -> is reached. so we keep looking for a -> until no more are found. if we have more than one, we'll add them to a list and then add that to the order Move order. */ LinkedList pathList = new LinkedList(); int idx = op.tokenIndex; int movTokIdx = findNextMoveToken(idx, tokens); while (movTokIdx != -1) { Location loc = parseLocation(pc, idx, movTokIdx, tokens); pathList.addLast(loc.getProvince()); idx = movTokIdx + 1; movTokIdx = findNextMoveToken(idx, tokens); } // add last location final Location loc = parseLocation(pc, idx, tokens.length, tokens); pathList.addLast(loc.getProvince()); // create Move order if (pathList.size() == 1) { if (pc.isRetreatPhase()) { return pc.orderFactory.createRetreat(op.power, op.location, op.unit, loc); } else { return pc.orderFactory.createMove(op.power, op.location, op.unit, loc); } } else if (pathList.size() > 1) { if (pc.isRetreatPhase()) { throw new OrderException("Convoyed Retreat orders are not allowed. Order: " + pc.orderText); } // add source location at beginning of move list pathList.addFirst(op.location.getProvince()); final Province[] route = (Province[]) pathList.toArray(new Province[pathList.size()]); return pc.orderFactory.createMove(op.power, op.location, op.unit, loc, route); } else { // this probably will not occur.... throw new OrderException("Invalid movement path in Move order: " + pc.orderText); } } // parseMove()
private NJudgeOrder parseAdjustmentOrder(ParseContext pc, String line) throws OrderException { Matcher m = ADJUSTMENT_PATTERN.matcher(line); // attempt if (m.find()) { /* Groups: 1: Power 2: remove|build|default (default is also a remove!) 3: fleet|army|wing 4: location */ // parse power final Power power = parsePower(pc, m.group(1).trim()); // parse unit type final Unit.Type unit = parseUnitType(pc, m.group(3)); // parse location final Location location = parseLocation(pc, m.group(4).trim()); // parse action final boolean isDefault = ("default".equalsIgnoreCase(m.group(2))); Orderable order = null; if ("build".equalsIgnoreCase(m.group(2))) { order = pc.orderFactory.createBuild(power, location, unit); } else { // remove or default order = pc.orderFactory.createRemove(power, location, unit); } NJudgeOrder njo = null; if (isDefault) { // power defaulted; order is contained in a substitutedResult SubstitutedResult substResult = new SubstitutedResult(null, order, "Power defaulted; unit removed."); njo = new NJudgeOrder(null, substResult, pc.isAdjustmentPhase()); } else { njo = new NJudgeOrder( order, new OrderResult(order, OrderResult.ResultType.SUCCESS, null), pc.isAdjustmentPhase()); } assert (njo != null); return njo; } else { // reuse variable 'm' m = ALTERNATE_ADJUSTMENT_PATTERN.matcher(line); if (m.find()) { // parse power final Power power = parsePower(pc, m.group(1).trim()); // parse # (may be empty) // (if it is empty, set to 0) int numBuilds = 0; try { if (m.group(2).length() >= 1) { numBuilds = Integer.parseInt(m.group(2)); } } catch (NumberFormatException e) { throw new OrderException( "Expected valid integer at \"" + m.group(2) + "\" for order: " + pc.orderText); } // parse if unused/unusable boolean isUnusable = false; final String group3Tok = m.group(3); if ("unused".equalsIgnoreCase(group3Tok) || "unusable".equalsIgnoreCase(group3Tok)) { isUnusable = true; } // parse if pending/waived final boolean isPending = "pending".equalsIgnoreCase(m.group(5)); final boolean isWaived = "waived".equalsIgnoreCase(m.group(5)); if (m.group(3) == null) { // Group3 is null when it is a 'regular' waive order. Result result = new Result(power, "Build waived."); return new NJudgeOrder(power, result); } else { // since we depend on external input, these should // be exceptions rather than asserts // assert (isUnusable); if (!isUnusable) { throw new IllegalStateException(); } assert (isPending != isWaived); if (isPending == isWaived) { throw new IllegalStateException(); } if (isPending) { // pending builds Result result = new Result(power, String.valueOf(numBuilds) + " unusable build(s) pending."); return new NJudgeOrder(power, numBuilds, 0, result); } else { // waived builds Result result = new Result(power, String.valueOf(numBuilds) + " unusable build(s) waived."); return new NJudgeOrder(power, 0, numBuilds, result); } } } else { throw new OrderException("Cannot parse adjustment order: " + pc.orderText); } } } // parseAdjustmentOrder()