/* * Copyright (C) 2017 by Fonoster Inc (http://fonoster.com) * http://astivetoolkit.org * * This file is part of Astive Toolkit(ATK) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.astivetoolkit.agi; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * * @since 1.0 */ public class AgiCommandReply { public static final int SC_TRYING = 100; public static final int SC_SUCCESS = 200; public static final int SC_INVALID_OR_UNKNOWN_COMMAND = 510; public static final int SC_DEAD_CHANNEL = 511; public static final int SC_INVALID_COMMAND_SYNTAX = 520; private static final Pattern STATUS_PATTERN = Pattern.compile("^(\\d{3})[ -]"); private static final Pattern RESULT_PATTERN = Pattern.compile("^200 result=(\\S+)"); private static final Pattern PARENTHESIS_PATTERN = Pattern.compile("^200 result=\\S* +\\((.*)\\)"); private static final Pattern ADDITIONAL_ATTRIBUTES_PATTERN = Pattern.compile("^200 result=\\S* +(\\(.*\\) )?(.+)$"); private static final Pattern SYNOPSIS_PATTERN = Pattern.compile("^\\s*Usage:\\s*(.*)\\s*$"); private static final String END_OF_PROPER_USAGE = "520 End of proper usage."; private Integer status; private List<String> lines; private Map<String, String> attributes; private String extra; private String firstLine; private String result; // In case of status == 520 (invalid command syntax) this attribute contains // the synopsis of the command. private String synopsis; // In case of status == 520 (invalid command syntax) this attribute contains //the usage of the command. private String usage; private boolean extraCreated; /** * Creates a new AgiCommandReply object. */ public AgiCommandReply() { super(); this.status = null; } /** * Creates a new AgiCommandReply object. * * @param lines */ public AgiCommandReply(List<String> lines) { this(); if (lines != null) { this.lines = new ArrayList<String>(lines); if (!lines.isEmpty()) { firstLine = lines.get(0); } } } public String getAttribute(String name) { if (getStatus() != SC_SUCCESS) { return null; } if ("result".equalsIgnoreCase(name)) { return getResult(); } return getAttributes().get(name.toLowerCase(Locale.ENGLISH)); } protected Map<String, String> getAttributes() { if (attributes != null) { return Collections.unmodifiableMap(attributes); } attributes = new HashMap<String, String>(); final Matcher matcher = ADDITIONAL_ATTRIBUTES_PATTERN.matcher(firstLine); if (matcher.find()) { attributes.putAll(parseAttributes(matcher.group(2))); } return Collections.unmodifiableMap(attributes); } public String getExtra() { if (getStatus() != SC_SUCCESS) { return null; } if (extraCreated) { return extra; } final Matcher matcher = PARENTHESIS_PATTERN.matcher(firstLine); if (matcher.find()) { extra = matcher.group(1); } extraCreated = true; return extra; } public String getFirstLine() { return firstLine; } public List<String> getLines() { return Collections.unmodifiableList(lines); } public String getResult() { if (result != null) { return result; } final Matcher matcher = RESULT_PATTERN.matcher(firstLine); if (matcher.find()) { result = matcher.group(1); } else { result = ""; } return result; } public int getResultCode() { result = getResult(); if (result == null) { return -1; } try { return Integer.parseInt(result); } catch (NumberFormatException e) { return -1; } } public char getResultCodeAsChar() { int resultCode; resultCode = getResultCode(); if (resultCode < 0) { return 0; } return (char) resultCode; } public int getStatus() { if (status != null) { return status; } final Matcher matcher = STATUS_PATTERN.matcher(firstLine); if (matcher.find()) { status = Integer.parseInt(matcher.group(1)); return status; } return -1; } public String getSynopsis() { if (getStatus() != SC_INVALID_COMMAND_SYNTAX) { return null; } if ((synopsis == null) && (lines.size() > 1)) { final String secondLine; final Matcher synopsisMatcher; secondLine = lines.get(1); synopsisMatcher = SYNOPSIS_PATTERN.matcher(secondLine); if (synopsisMatcher.find()) { synopsis = synopsisMatcher.group(1); } } return synopsis; } /** * Returns the usage of the command sent if Asterisk expected a different * syntax (getStatus() == SC_INVALID_COMMAND_SYNTAX). * * @return the usage of the command sent, <code>null</code> if there were no * syntax errors. */ public String getUsage() { if (usage == null) { StringBuilder usageSB; usageSB = new StringBuilder(); for (int i = 2; i < lines.size(); i++) { String line; line = lines.get(i); if (END_OF_PROPER_USAGE.equals(line)) { break; } usageSB.append(line.trim()); usageSB.append(" "); } usage = usageSB.toString().trim(); } return usage; } private Map<String, String> parseAttributes(String s) { StringBuilder keyBuilder = new StringBuilder(); StringBuilder valueBuilder = new StringBuilder(); Map<String, String> map = new HashMap<String, String>(); boolean inKey = true; boolean inQuotes = false; char previousChar = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if ((c == '=') && inKey) { inKey = false; inQuotes = false; } else if (((c == ' ') && !inKey && !inQuotes)) { map.put(keyBuilder.toString().toLowerCase(Locale.ENGLISH), valueBuilder.toString()); keyBuilder.delete(0, keyBuilder.length()); valueBuilder.delete(0, valueBuilder.length()); inKey = true; } else if ((c == '"') && !inKey) { if (previousChar == '\\') { valueBuilder.deleteCharAt(valueBuilder.length() - 1); valueBuilder.append(c); } else { inQuotes = !inQuotes; } } else { if (inKey) { keyBuilder.append(c); } else { valueBuilder.append(c); } } previousChar = c; } if (keyBuilder.length() > 0) { map.put(keyBuilder.toString().toLowerCase(Locale.ENGLISH), valueBuilder.toString()); } return map; } @Override public String toString() { StringBuilder sb; sb = new StringBuilder("AgiReply["); sb.append("status=").append(getStatus()).append(","); if (status == SC_SUCCESS) { sb.append("result='").append(getResult()).append("',"); sb.append("extra='").append(getExtra()).append("',"); sb.append("attributes=").append(getAttributes()).append(","); } if (status == SC_INVALID_COMMAND_SYNTAX) { sb.append("synopsis='").append(getSynopsis()).append("',"); } sb.append("systemHashcode=").append(System.identityHashCode(this)); sb.append("]"); return sb.toString(); } }