Задача: необходимо организовать быстрый и удобный способ получения объектов и их параметров от Java Web приложения.
Я пишу движок браузерной РПГ. Вся игровая логика (переходы между локациями, покупка вещей и т.п.) обслуживает Tomcat 6. Страничка запрашивает JSON объект, сервер обрабатывает запрос, и отдает результат. В начале у меня на каждую группу действий был отдельный сервлет, было много кода и дупликаций. Вобщем, мне не нравилось.
Все обращения к серверу деляться на два типа:
1. получение состояния объекта (имя игрока, его уровень, список игроков на локации)
2. изменение состояния объекта (покупка вещи, сборка предмета, отправка сообщения)
Обработку второго типа я тоже упростил, но о нем в следующий раз.
Идея заключается в обобщении всех объектов, чтобы построить простой алгоритм получения результата.
Для этого я ипользовал интерфес, а объекты его реализовали.
основными методами являютяс:
Несколько примеров реализации этих методов:
Немного о самой задачи.
Я пишу движок браузерной РПГ. Вся игровая логика (переходы между локациями, покупка вещей и т.п.) обслуживает Tomcat 6. Страничка запрашивает JSON объект, сервер обрабатывает запрос, и отдает результат. В начале у меня на каждую группу действий был отдельный сервлет, было много кода и дупликаций. Вобщем, мне не нравилось.
Все обращения к серверу деляться на два типа:
1. получение состояния объекта (имя игрока, его уровень, список игроков на локации)
2. изменение состояния объекта (покупка вещи, сборка предмета, отправка сообщения)
Обработку второго типа я тоже упростил, но о нем в следующий раз.
Идея заключается в обобщении всех объектов, чтобы построить простой алгоритм получения результата.
Для этого я ипользовал интерфес, а объекты его реализовали.
package engine.core.obj;
import org.json.JSONObject;
/**
*
* @author chernser
* Interface used to work with core objects
*
*/
public interface ICoreObject {
/**
* Should implement encoding into
* JSON notation
* @param context TODO
*
* @return returns object in JSON notation
* @return null if error
*/
public JSONObject toJSON(Context context);
/**
*
* Should implement getting object's class
* name
*
* @return returns object class name
* @return null if error
*/
public String getClassName();
/**
* Should find child object by name
* and return it
* @param context TODO
*
* @return returns child object
* @return null if error
*/
public ICoreObject getChild(String name, Context context);
/**
* Should find child object
* by name and index then return it
* @param context TODO
*
* @return returns child object
* @return null if error
*/
public ICoreObject getChild(String name, Integer index, Context context);
/**
* Should return parameter's value
*
* @param name - parameter name
* @param context - current context
* @return returns value of parameter
* @return null if no such parameter
*
*/
public Object getParameter(String name, Context context);
}
* This source code was highlighted with Source Code Highlighter.
основными методами являютяс:
- toJSON() — собственно формирование объекта
- getChild() — возвращает потомка текущего объекта
- getParameter() — возвращает один из параметров объекта, например, имя игрока
Несколько примеров реализации этих методов:
@Override
public ICoreObject getChild(String name, Context context) {
if (name.equals(AvatarBaseParams.CORE_OBJ_NAME)) {
return itsBaseParams;
} else if (name.equals(Location.CORE_OBJ_NAME)) {
return itsLocation;
} else if (name.equals(AvatarInventory.CORE_OBJ_NAME))
{
return itsInventory;
} else if (name.equals(AvatarSkills.CORE_OBJ_NAME))
{
return itsSkills;
} else if (name.equals(AvatarModifications.CORE_OBJ_NAME))
{
return itsModifications;
}
return null;
}
* This source code was highlighted with Source Code Highlighter.
Этот метод, как правило, простой диспатчер. Благодаря данному методу можно строить любую иерархию.
@Override
public JSONObject toJSON(Context context) {
JSONObject result = new JSONObject();
try {
result.put("name", itsName);
result.put("bparams", itsBaseParams.toJSON(context));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return result;
}
* This source code was highlighted with Source Code Highlighter.
Здесь создаем JSONObject с нужными нам полями.
Так у нас есть объекты подчиненные общим правилам. Нам нужен алгоритм быстрого прохождения по все цепочке и возвращения результата. Тут, пожалуй, самый сложный момент :-)
Строка запроса выглядит примерно так:
user.avatar:name — возвращает name объекта avatar. Для доступа к параметрам используется двоеточие
user.avatar.location — возвращает JSON представление объекта location.
user.avatar.inventory.slot[4] — возвращает JSON представление 4го слота в инвентаре игрока.
Запросы попадают в отдельный сервлет и вот как обрабатываются:
1. Начальная проверка запроса и возврат значения
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* Set encoding for request to properly parse parameters */
request.setCharacterEncoding("UTF-8");
// get user from session
User user = (User) request.getSession().getAttribute(
CommonConstants.USER);
// save query and prelimenary result
String query = request.getParameter("query");
String result = "{error: -1}";
// check if we have user
if (user != null)
{
Context context = user.itsContext;
// Check parameters
if (query != null)
{
// process query
result = ParseAndExecuteQuery(query, request.getSession(), context);
}
}
/* tune response to avoid problem with Russian */
response.setContentType("text/javascript; charset=UTF-8");
response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1
response.setHeader("Pragma", "no-cache"); //HTTP 1.0
response.setDateHeader("Expires", 0); //prevents caching at the proxy server
response.getWriter().write(result);
}
* This source code was highlighted with Source Code Highlighter.
2. Подготовка запроса и определение начального объекта ( у меня их два — пользователь и мир)
private String ParseAndExecuteQuery(String query, HttpSession session, Context context)
{
ICoreObject rootObject = null;
// if query starts from "user." Also check syntax
if (query.matches("user\\.([a-zA-Z_]+(\\[[0-9]+\\])*(:[a-zA-Z_]+)*\\.{0,1})+"))
{
rootObject = (User) session.getAttribute(CommonConstants.USER);
}
// if query starts from "world." Also check syntax
else if (query.matches("world\\.([a-zA-Z_]+(\\[[0-9]+\\])*(:[a-zA-Z_]+)*\\.{0,1})+"))
{
rootObject = (World)session.getServletContext().
getAttribute(CommonConstants.WORLD);
}
// Check if we have root object
if (rootObject != null)
{
// Start processing
ICoreObject result = getObject(rootObject, query, context);
if (result != null)
{
// Here we got result
JSONObject jsonObj = result.toJSON(context);
if (jsonObj != null)
{
// return it
return jsonObj.toString();
}
}
}
return "";
}
* This source code was highlighted with Source Code Highlighter.
3. Нахождение нужного объекта
private ICoreObject getObject(ICoreObject rootObject, String query, Context context)
{
ICoreObject curObject = rootObject; // current object
query = query.substring(query.indexOf(".") + 1) + "."; // query
Integer dotIndex = query.indexOf(".");
String tmpObjName = ""; // object name
do {
String objName = query.substring(0, dotIndex); // get first object in chain
// if by index
if (objName.matches("[a-zA-Z_]+\\[[0-9]+\\](:[a-zA-Z0-9_]+)*"))
{
Integer index = new Integer(objName.substring(objName.indexOf("[") + 1,
objName.indexOf("]")));
tmpObjName = objName.substring(0, objName.indexOf("["));
curObject = curObject.getChild(tmpObjName, index, context);
}
else // if not by index
{
Integer semiIndex = 0;
if ((semiIndex = objName.indexOf(":")) > -1)
curObject = curObject.getChild(objName.substring(0, semiIndex), context);
else
curObject = curObject.getChild(objName, context);
}
// If current object in chain contains ':' - then proccess it as parameter
if (objName.matches("[a-zA-Z_]+(\\[[0-9]+\\])*:[a-zA-Z0-9_]+"))
{
// get parameter name
String param = objName.substring(objName.indexOf(":") + 1);
return new CoreObjectParameter(param, curObject.getParameter(param, context));
}
// cut off current object name and step forward
query = query.substring(dotIndex + 1);
dotIndex = query.indexOf(".");
} while ((dotIndex > -1) && (curObject != null));
return curObject;
}
* This source code was highlighted with Source Code Highlighter.
Если объект найден, он передается в виде JSON скрипту на странице, если нет — возвращается пустой объект.
Механизм пока на стадии бета :-) Пока, правда, багов не проявил.