Category Archives: Java
JavaCC позволяет нам работать с набором токенов. Но часто бывает нужно сделать так, чтобы в некотором месте лексер не использовал набор основных токенов, а работал бы иначе. А потом снова возвращался к прежнему состоянию. Например, это может понадобиться для обработки многострочных комментариев – после встречи токена начала такого комментария лексер должен переключиться на режим, в котором бы игнорировалось всё, кроме токена его окончания. В случае комментариев это можно сделать стандартным для JavaCC способом – в настройке токенайзера:
SKIP : { // Однострочный комментарий < "//" (~["\r", "\n"])* > // Начало многострочного комментария - переход к другому состоянию лексера | < "/*" > : ML_COMMENT_STATE } // В этом состоянии есть существуют только два токена: конец комментария // и всё остальное. По нахождении токена конца комментария состояние возвращается в DEFAULT <ML_COMMENT_STATE> SKIP : { < "*/" > : DEFAULT | < ~[] > } |
А иногда бывает так, что перейти к другому набору токенов нужно не в токенайзере, а именно в парсере. Например, когда парсер встретил специальный токен и потом идут данные в другом формате. При парсинге различных DSL это может встречаться, например, в следующем варианте. Допустим у нас есть токен t_identifier, который начинается с буквы и далее идут буквы или цифры с подчёркиваниями. То есть – обычный идентификатор. Но мы хотим добавить поддержку директивы, синтаксис которой содержит ключевое слово, которое является валидным идентификатором. Например, “ignore”. Если не добавить специальный токен для этой сигнатуры, лексер при встрече с ним выплюнет нам токен-идентификатор. А если добавить токен
SKIP : { " " | "\t" | "\n" | "\r" } TOKEN: { <t_cat: "category"> | < tt_identifier: <LETTER> ( <LETTER>|<DIGIT> )* > // Строка в кавычках | < tt_string: "\"" (~["\"","\\","\n","\r"] | "\\" (["n","t","b","r","f","\\","\'","\""] | ["0"-"7"] (["0"-"7"])? | ["0"-"3"] ["0"-"7"] ["0"-"7"]))* "\"" > | < #LETTER: [ "_", "a"-"z", "A"-"Z", "а"-"я", "А"-"Я" ] > | < #DIGIT: [ "0"-"9"] > } // Да, для каждого лексического состояния нужно определить не только TOKEN, но и SKIP // и всё остальное, что используется <MYSTATE> SKIP : { " " | "\t" | "\n" | "\r" } <MYSTATE> TOKEN: { < tt_mystate_string: "\"" (~["\"","\\","\n","\r"] | "\\" (["n","t","b","r","f","\\","\'","\""] | ["0"-"7"] (["0"-"7"])? | ["0"-"3"] ["0"-"7"] ["0"-"7"]))* "\"" > | <tt_mystate_ignore: "ignore" > | <tt_mystate_semicolon: ";"> } // Часть того самого хака, необходимая для работы функции SetState(). TOKEN_MGR_DECLS : { void backup(int n) { input_stream.backup(n); } } // Функция SetState() необходима для безопасного перехода к другому лексическому состоянию // (так как токенайзер конвейеризирует поток токенов, и нужно этот конвейер сбросить). JAVACODE private void SetState(int state) { if (state != token_source.curLexState) { Token root = new Token(), last=root; root.next = null; // First, we build a list of tokens to push back, in backwards order while (token.next != null) { Token t = token; // Find the token whose token.next is the last in the chain while (t.next != null && t.next.next != null) t = t.next; // put it at the end of the new chain last.next = t.next; last = t.next; // If there are special tokens, these go before the regular tokens, // so we want to push them back onto the input stream in the order // we find them along the specialToken chain. if (t.next.specialToken != null) { Token tt=t.next.specialToken; while (tt != null) { last.next = tt; last = tt; tt.next = null; tt = tt.specialToken; } } t.next = null; }; while (root.next != null) { token_source.backup(root.next.image.length()); root.next = root.next.next; } jj_ntk = -1; token_source.SwitchTo(state); } } // Далее уже идёт наш код парсера private void Category() : { Token tname; } { <t_cat> <tt_string> ( "my state" MyState() | ";" ) } private void MyState() : { int entryState = token_source.curLexState; } { { SetState(MYSTATE); } <tt_mystate_string> [<tt_mystate_ignore> <tt_mystate_string>] <tt_mystate_semicolon> { SetState(entryState); } } |
Неудобство заключается в том, что в изменённом (не-DEFAULT) состоянии нельзя использовать строковые литералы просто так, не добавляя для них токены (то есть вместо “;” мы должны записать <tt_mystate_semicolon>). Возможно, это баг, но может оказаться и специальным ограничением. Ветку с обсуждением этой проблемы можно прочитать здесь. Впрочем, несмотря на эту проблему, у нас всё получилось, и мы вернули себе полный контроль над процессом.
Простая листалка на JSP, подходит для админок. Для использования нужно заинклудить этот файл в основную JSP страницы, а также установить переменные url, total_records, current_page и page_size. url – базовый url для этой страницы (к нему через амперсанд будет дописываться параметр page). total_records – сколько всего записей имеется, current_page – индекс текущей страницы, начиная с нуля. page_size – размер страницы. Можно настроить, сколько страниц отображать слева и справа от текущей (если мы находимся глубоко в середине).
Выглядит примерно так:
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8" session="false" %> <%--@elvariable id="url" type="java.lang.String"--%> <%--@elvariable id="total_records" type="java.lang.Integer"--%> <%--@elvariable id="current_page" type="java.lang.Integer"--%> <%--@elvariable id="page_size" type="java.lang.Integer"--%> <style type="text/css"> div.pagination { margin-top: 10px; text-align: center; } .pagination a { color: #3b5998; text-decoration: none; border: 1px #c2d1df solid; padding: 2px 5px; margin-right: 5px; } .pagination a:hover { color: #3b5998; text-decoration: none; border: 1px #3b5998 solid; } a.current { background: #c2d6ed; } </style> <div class="pagination"> <a href="${url}&page=${current_page-1>=0?current_page-1:0}" class="prev">< Prev</a> <% Integer total_records = (Integer) request.getAttribute("total_records"); Integer page_size = (Integer) request.getAttribute("page_size"); Integer current_page = (Integer) request.getAttribute( "current_page" ); int pages = total_records / page_size; int lastPage = pages * page_size < total_records ? pages : pages - 1; request.setAttribute("last_page", lastPage); // сколько ссылок отображается начиная с самой первой (не может быть установлено в 0) final int N_PAGES_FIRST = 1; // сколько ссылок отображается слева от текущей (может быть установлено в 0) final int N_PAGES_PREV = 1; // сколько ссылок отображается справа от текущей (может быть установлено в 0) final int N_PAGES_NEXT = 1; // сколько ссылок отображается в конце списка страниц (не может быть установлено в 0) final int N_PAGES_LAST = 1; if (N_PAGES_FIRST < 1 || N_PAGES_LAST < 1) throw new AssertionError( ); // показывать ли полностью все ссылки на страницы слева от текущей, или вставить многоточие boolean showAllPrev; // показывать ли полностью все ссылки на страницы справа от текущей, или вставить многоточие boolean showAllNext; showAllPrev = N_PAGES_FIRST >= (current_page - N_PAGES_PREV); showAllNext = current_page + N_PAGES_NEXT >= lastPage - N_PAGES_LAST; request.setAttribute( "N_PAGES_FIRST", N_PAGES_FIRST ); request.setAttribute( "N_PAGES_PREV", N_PAGES_PREV ); request.setAttribute( "N_PAGES_NEXT", N_PAGES_NEXT ); request.setAttribute( "N_PAGES_LAST", N_PAGES_LAST ); request.setAttribute( "showAllPrev", showAllPrev ); request.setAttribute( "showAllNext", showAllNext ); %> <%-- show left pages --%> <c:choose> <c:when test="${showAllPrev}"> <c:if test="${current_page > 0}"> <c:forEach begin="0" end="${current_page - 1}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> </c:if> </c:when> <c:otherwise> <c:forEach begin="0" end="${N_PAGES_FIRST - 1}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> <span style="margin-right: 5px">...</span> <c:forEach begin="${current_page - N_PAGES_PREV}" end="${current_page - 1}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> </c:otherwise> </c:choose> <%-- show current page --%> <a href="${url}&page=${current_page}" class="current">${current_page + 1}</a> <%-- show right pages --%> <c:choose> <c:when test="${showAllNext}"> <c:forEach begin="${current_page + 1}" end="${last_page}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> </c:when> <c:otherwise> <c:forEach begin="${current_page + 1}" end="${current_page + 1 + (N_PAGES_NEXT - 1)}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> <span style="margin-right: 5px">...</span> <c:forEach begin="${last_page - (N_PAGES_LAST - 1)}" end="${last_page}" var="p"> <a href="${url}&page=${p}">${p + 1}</a> </c:forEach> </c:otherwise> </c:choose> <a href="${url}&page=${current_page + 1 > last_page ? last_page : current_page + 1}" class="next">Next ></a> </div> |
Задача
Есть многомодульный проект, собираемый с помощью maven. Среди модулей имеются несколько WAR-приложений, которые должны деплоиться в сервлет-контейнер (рассмотрим Томкат, но всё применимо и к любому другому контейнеру или серверу приложений). Причём желательно иметь возможность выборочно собирать и деплоить только одно из этих приложений. Развёртываться всё это добро должно уметь как в локальный контейнер программиста, так и на удалённый тестовый сервер.
Решение
У нас есть один главный модуль (тот, у которого packaging = pom) и несколько модулей. В pom.xml главного модуля мы пропишем два набора профилей. Один набор профилей будет отвечать за то, собирать ли указанный модуль или нет. Второй набор профилей будет отвечать за настройки окружения. Таким образом, мы превращаем мейвеновские профили в аналог USE-флагов в Gentoo Linux. Также в pom.xml главного модуля мы сконфигурируем плагин cargo-maven2-plugin, указав ему skip = true. А в тех дочерних модулях, которые предполагаются к развёртыванию, мы этот флаг установим в false. Таким образом, мы сможем вызывать cargo:deploy для главного модуля, но срабатывать он будет только у тех дочерних, которые мы укажем. Иные варианты, к сожалению, работать не будут (например, если конфигурировать плагин для дочернего модуля и вызывать напрямую у него, то при наличии зависимостей maven не сможет собрать модуль). А два набора профилей дадут нам возможность выборочно собирать и развёртывать приложения туда, куда нам хочется.
Код
pom.xml главного модуля:
<modules> <!-- Модули, собираемые всегда --> <module>common-dependency</module> </modules> <packaging>pom</packaging> <profiles> <!-- Профили, относящиеся к настройкам окружения - при сборке мы должны указать только 1 профиль --> <profile> <id>env-igor-dev</id> <properties> <tomcatHost>localhost</tomcatHost> <tomcatPort>8091</tomcatPort> <tomcatManagerUser>tomcat</tomcatManagerUser> <tomcatManagerPassword>1</tomcatManagerPassword> </properties> </profile> <profile> <id>env-test</id> <properties> <tomcatHost>test.com</tomcatHost> <tomcatPort>8080</tomcatPort> <tomcatManagerUser>tom</tomcatManagerUser> <tomcatManagerPassword>fsKf2_3</tomcatManagerPassword> </properties> </profile> <!-- Профили, относящиеся к тому, что мы хотим собрать. При сборке можно указать любую комбинацию этих профилей. --> <profile> <id>build-war1</id> <modules> <module>webapp-1</module> </modules> </profile> <profile> <id>build-war2</id> <modules> <module>webapp-2</module> </modules> </profile> </profiles> <build> <plugins> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <configuration> <configuration> <properties> <cargo.hostname>${tomcatHost}</cargo.hostname> <cargo.protocol>http</cargo.protocol> <cargo.servlet.port>${tomcatPort}</cargo.servlet.port> <cargo.remote.username>${tomcatManagerUser}</cargo.remote.username> <cargo.remote.password>${tomcatManagerPassword}</cargo.remote.password> </properties> <type>runtime</type> </configuration> <container> <containerId>tomcat6x</containerId> <type>remote</type> </container> <!-- skip in parent pom and by default in all submodules --> <skip>true</skip> </configuration> </plugin> </plugins> </build> |
pom.xml одного из приложений:
<build> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> <configuration> <warName>war1</warName> </configuration> </plugin> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <configuration> <deployables> <deployable> <location>webapp-1/target/war1.war</location> </deployable> </deployables> <skip>false</skip> </configuration> </plugin> </plugins> </build> |
pom.xml второго приложения составляется аналогично
Результат
Приложение собирается и деплоится одной командой
mvn clean package cargo:redeploy -P env-igor-dev,build-war1,build-war2
Update
Если у вас в war-приложении есть META-INF/context.xml
, а в нём задан context path (например <Context antiJARLocking="true" path="/">
), то cargo проигнорирует свойство deployable -> properties -> context
и загрузит варник в ROOT.war
. Поэтому если вам нужно, чтобы варник деплоился туда куда надо, настройте maven resources plugin с filtering=true чтобы path устанавливать в нужное вам значение.
0