/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.jackrabbit.commons.cnd; import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory.AbstractNodeDefinitionBuilder; import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory.AbstractNodeTypeDefinitionBuilder; import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory.AbstractPropertyDefinitionBuilder; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.query.qom.QueryObjectModelConstants; import javax.jcr.version.OnParentVersionAction; import java.io.Reader; import java.util.LinkedList; import java.util.List; /** * CompactNodeTypeDefReader. Parses node type definitions written in the compact * node type definition format and provides a list of type definition * objects that can then be used to register node types. * <p> * The CompactNodeTypeDefReader is parameterizable in the type of the node type * definition <code>T</code> and the type of the namespace mapping <code>N</code> * which the parser should build. For types <code>T</code> and <code>N</code> the * parser's constructor takes a {@link DefinitionBuilderFactory} for * <code>T</code> and <code>N</code>. * <p> * <p> * The EBNF grammar of the compact node type definition:<br> * <pre> * Cnd ::= {NamespaceMapping | NodeTypeDef} * NamespaceMapping ::= '<' Prefix '=' Uri '>' * Prefix ::= String * Uri ::= String * NodeTypeDef ::= NodeTypeName [Supertypes] * [NodeTypeAttribute {NodeTypeAttribute}] * {PropertyDef | ChildNodeDef} * NodeTypeName ::= '[' String ']' * Supertypes ::= '>' (StringList | '?') * NodeTypeAttribute ::= Orderable | Mixin | Abstract | Query | * PrimaryItem * Orderable ::= ('orderable' | 'ord' | 'o') ['?'] * Mixin ::= ('mixin' | 'mix' | 'm') ['?'] * Abstract ::= ('abstract' | 'abs' | 'a') ['?'] * Query ::= ('noquery' | 'nq') | ('query' | 'q' ) * PrimaryItem ::= ('primaryitem'| '!')(String | '?') * PropertyDef ::= PropertyName [PropertyType] [DefaultValues] * [PropertyAttribute {PropertyAttribute}] * [ValueConstraints] * PropertyName ::= '-' String * PropertyType ::= '(' ('STRING' | 'BINARY' | 'LONG' | 'DOUBLE' | * 'BOOLEAN' | 'DATE' | 'NAME' | 'PATH' | * 'REFERENCE' | 'WEAKREFERENCE' | * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | * '?') ')' * DefaultValues ::= '=' (StringList | '?') * ValueConstraints ::= '<' (StringList | '?') * ChildNodeDef ::= NodeName [RequiredTypes] [DefaultType] * [NodeAttribute {NodeAttribute}] * NodeName ::= '+' String * RequiredTypes ::= '(' (StringList | '?') ')' * DefaultType ::= '=' (String | '?') * PropertyAttribute ::= Autocreated | Mandatory | Protected | * Opv | Multiple | QueryOps | NoFullText | * NoQueryOrder * NodeAttribute ::= Autocreated | Mandatory | Protected | * Opv | Sns * Autocreated ::= ('autocreated' | 'aut' | 'a' )['?'] * Mandatory ::= ('mandatory' | 'man' | 'm') ['?'] * Protected ::= ('protected' | 'pro' | 'p') ['?'] * Opv ::= 'COPY' | 'VERSION' | 'INITIALIZE' | 'COMPUTE' | * 'IGNORE' | 'ABORT' | ('OPV' '?') * Multiple ::= ('multiple' | 'mul' | '*') ['?'] * QueryOps ::= ('queryops' | 'qop') * (('''Operator {','Operator}''') | '?') * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' * NoFullText ::= ('nofulltext' | 'nof') ['?'] * NoQueryOrder ::= ('noqueryorder' | 'nqord') ['?'] * Sns ::= ('sns' | '*') ['?'] * StringList ::= String {',' String} * String ::= QuotedString | UnquotedString * QuotedString ::= SingleQuotedString | DoubleQuotedString * SingleQuotedString ::= ''' UnquotedString ''' * DoubleQuotedString ::= '"' UnquotedString '"' * UnquotedString ::= XmlChar {XmlChar} * XmlChar ::= see 3.2.2 Local Names * </pre> * * @param <T> * @param <N> */ public class CompactNodeTypeDefReader<T, N> { /** * the list of parsed QNodeTypeDefinition */ private final List<T> nodeTypeDefs = new LinkedList<T>(); /** * the underlying lexer */ private final Lexer lexer; /** * the current token */ private String currentToken; /** * The builder for QNodeTypeDefinitions */ private final DefinitionBuilderFactory<T, N> factory; /** * Creates a new CND reader and parses the given stream. * * @param r a reader to the CND * @param systemId a informative id of the given stream * @param factory builder for creating new definitions and handling namespaces * @throws ParseException if an error occurs */ public CompactNodeTypeDefReader(Reader r, String systemId, DefinitionBuilderFactory<T, N> factory) throws ParseException { this(r, systemId, null, factory); } /** * Creates a new CND reader and parses the given stream. * * @param r a reader to the CND * @param systemId a informative id of the given stream * @param nsMapping default namespace mapping to use * @param factory builder for creating new definitions and handling namespaces * @throws ParseException if an error occurs */ public CompactNodeTypeDefReader(Reader r, String systemId, N nsMapping, DefinitionBuilderFactory<T, N> factory) throws ParseException { super(); this.factory = factory; lexer = new Lexer(r, systemId); if (nsMapping != null) { factory.setNamespaceMapping(nsMapping); } nextToken(); parse(); } /** * Returns the previously assigned system id * * @return the system id */ public String getSystemId() { return lexer.getSystemId(); } /** * Returns the list of parsed node type definitions definitions. * * @return a collection of node type definition objects */ public List<T> getNodeTypeDefinitions() { return nodeTypeDefs; } /** * Returns the namespace mapping. * * @return */ public N getNamespaceMapping() { return factory.getNamespaceMapping(); } /** * Parses the definition * * @throws ParseException if an error during parsing occurs */ private void parse() throws ParseException { while (!currentTokenEquals(Lexer.EOF)) { if (!doNameSpace()) { break; } } try { while (!currentTokenEquals(Lexer.EOF)) { AbstractNodeTypeDefinitionBuilder<T> ntd = factory.newNodeTypeDefinitionBuilder(); ntd.setOrderableChildNodes(false); ntd.setMixin(false); ntd.setAbstract(false); ntd.setQueryable(true); doNodeTypeName(ntd); doSuperTypes(ntd); doOptions(ntd); doItemDefs(ntd); nodeTypeDefs.add(ntd.build()); } } catch (RepositoryException e) { lexer.fail(e); } } /** * processes the namespace declaration * * @return <code>true</code> if a namespace was parsed * @throws ParseException if an error during parsing occurs */ private boolean doNameSpace() throws ParseException { if (!currentTokenEquals('<')) { return false; } nextToken(); String prefix = currentToken; nextToken(); if (!currentTokenEquals('=')) { lexer.fail("Missing = in namespace decl."); } nextToken(); String uri = currentToken; nextToken(); if (!currentTokenEquals('>')) { lexer.fail("Missing > in namespace decl."); } try { factory.setNamespace(prefix, uri); } catch (RepositoryException e) { lexer.fail("Error setting namespace mapping " + currentToken, e); } nextToken(); return true; } /** * processes the nodetype name * * @param ntd nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doNodeTypeName(AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { if (!currentTokenEquals(Lexer.BEGIN_NODE_TYPE_NAME)) { lexer.fail("Missing '" + Lexer.BEGIN_NODE_TYPE_NAME + "' delimiter for beginning of node type name"); } nextToken(); try { ntd.setName(currentToken); } catch (RepositoryException e) { lexer.fail("Error setting node type name " + currentToken, e); } nextToken(); if (!currentTokenEquals(Lexer.END_NODE_TYPE_NAME)) { lexer.fail("Missing '" + Lexer.END_NODE_TYPE_NAME + "' delimiter for end of node type name, found " + currentToken); } nextToken(); } /** * processes the superclasses * * @param ntd nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doSuperTypes(AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { if (currentTokenEquals(Lexer.EXTENDS)) do { nextToken(); try { ntd.addSupertype(currentToken); } catch (RepositoryException e) { lexer.fail("Error setting super type of " + ntd.getName() + " to " + currentToken, e); } nextToken(); } while (currentTokenEquals(Lexer.LIST_DELIMITER)); } /** * processes the options * * @param ntd nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doOptions(AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { boolean hasOption = true; try { while (hasOption) { if (currentTokenEquals(Lexer.ORDERABLE)) { nextToken(); ntd.setOrderableChildNodes(true); } else if (currentTokenEquals(Lexer.MIXIN)) { nextToken(); ntd.setMixin(true); } else if (currentTokenEquals(Lexer.ABSTRACT)) { nextToken(); ntd.setAbstract(true); } else if (currentTokenEquals(Lexer.NOQUERY)) { nextToken(); ntd.setQueryable(false); } else if (currentTokenEquals(Lexer.QUERY)) { nextToken(); ntd.setQueryable(true); } else if (currentTokenEquals(Lexer.PRIMARYITEM)) { nextToken(); ntd.setPrimaryItemName(currentToken); nextToken(); } else { hasOption = false; } } } catch (RepositoryException e) { lexer.fail("Error setting option of " + ntd.getName() + " to " + currentToken, e); } } /** * processes the item definitions * * @param ntd nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doItemDefs(AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { while (currentTokenEquals(Lexer.PROPERTY_DEFINITION) || currentTokenEquals(Lexer.CHILD_NODE_DEFINITION)) { if (currentTokenEquals(Lexer.PROPERTY_DEFINITION)) { try { AbstractPropertyDefinitionBuilder<T> pd = ntd.newPropertyDefinitionBuilder(); try { pd.setAutoCreated(false); pd.setDeclaringNodeType(ntd.getName()); pd.setMandatory(false); pd.setMultiple(false); pd.setOnParentVersion(OnParentVersionAction.COPY); pd.setProtected(false); pd.setRequiredType(PropertyType.STRING); pd.setFullTextSearchable(true); pd.setQueryOrderable(true); } catch (RepositoryException e) { lexer.fail("Error setting property definitions of " + pd.getName() + " to " + currentToken, e); } nextToken(); doPropertyDefinition(pd, ntd); pd.build(); } catch (RepositoryException e) { lexer.fail("Error building property definition for " + ntd.getName(), e); } } else if (currentTokenEquals(Lexer.CHILD_NODE_DEFINITION)) { try { AbstractNodeDefinitionBuilder<T> nd = ntd.newNodeDefinitionBuilder(); try { nd.setAllowsSameNameSiblings(false); nd.setAutoCreated(false); nd.setDeclaringNodeType(ntd.getName()); nd.setMandatory(false); nd.setOnParentVersion(OnParentVersionAction.COPY); nd.setProtected(false); } catch (RepositoryException e) { lexer.fail("Error setting node definitions of " + nd.getName() + " to " + currentToken, e); } nextToken(); doChildNodeDefinition(nd, ntd); nd.build(); } catch (RepositoryException e) { lexer.fail("Error building node definition for " + ntd.getName(), e); } } } } /** * processes the property definition * * @param pd property definition builder * @param ntd declaring nodetype definition builder * @throws ParseException if an error during parsing occur */ private void doPropertyDefinition(AbstractPropertyDefinitionBuilder<T> pd, AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { try { pd.setName(currentToken); } catch (RepositoryException e) { lexer.fail("Invalid property name '" + currentToken, e); } nextToken(); doPropertyType(pd); doPropertyDefaultValue(pd); doPropertyAttributes(pd, ntd); doPropertyValueConstraints(pd); } /** * processes the property type * * @param pd property definition builder * @throws ParseException if an error during parsing occurs */ private void doPropertyType(AbstractPropertyDefinitionBuilder<T> pd) throws ParseException { if (!currentTokenEquals(Lexer.BEGIN_TYPE)) { return; } nextToken(); try { if (currentTokenEquals(Lexer.STRING)) { pd.setRequiredType(PropertyType.STRING); } else if (currentTokenEquals(Lexer.BINARY)) { pd.setRequiredType(PropertyType.BINARY); } else if (currentTokenEquals(Lexer.LONG)) { pd.setRequiredType(PropertyType.LONG); } else if (currentTokenEquals(Lexer.DECIMAL)) { pd.setRequiredType(PropertyType.DECIMAL); } else if (currentTokenEquals(Lexer.DOUBLE)) { pd.setRequiredType(PropertyType.DOUBLE); } else if (currentTokenEquals(Lexer.BOOLEAN)) { pd.setRequiredType(PropertyType.BOOLEAN); } else if (currentTokenEquals(Lexer.DATE)) { pd.setRequiredType(PropertyType.DATE); } else if (currentTokenEquals(Lexer.NAME)) { pd.setRequiredType(PropertyType.NAME); } else if (currentTokenEquals(Lexer.PATH)) { pd.setRequiredType(PropertyType.PATH); } else if (currentTokenEquals(Lexer.URI)) { pd.setRequiredType(PropertyType.URI); } else if (currentTokenEquals(Lexer.REFERENCE)) { pd.setRequiredType(PropertyType.REFERENCE); } else if (currentTokenEquals(Lexer.WEAKREFERENCE)) { pd.setRequiredType(PropertyType.WEAKREFERENCE); } else if (currentTokenEquals(Lexer.UNDEFINED)) { pd.setRequiredType(PropertyType.UNDEFINED); } else { lexer.fail("Unkown property type '" + currentToken + "' specified"); } } catch (RepositoryException e) { lexer.fail("Error setting property type of " + pd.getName() + " to " + currentToken, e); } nextToken(); if (!currentTokenEquals(Lexer.END_TYPE)) { lexer.fail("Missing '" + Lexer.END_TYPE + "' delimiter for end of property type"); } nextToken(); } /** * processes the property attributes * * @param pd property definition builder * @param ntd declaring nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doPropertyAttributes(AbstractPropertyDefinitionBuilder<T> pd, AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { try { while (currentTokenEquals(Lexer.PROP_ATTRIBUTE)) { if (currentTokenEquals(Lexer.PRIMARY)) { ntd.setPrimaryItemName(pd.getName()); } else if (currentTokenEquals(Lexer.AUTOCREATED)) { pd.setAutoCreated(true); } else if (currentTokenEquals(Lexer.MANDATORY)) { pd.setMandatory(true); } else if (currentTokenEquals(Lexer.PROTECTED)) { pd.setProtected(true); } else if (currentTokenEquals(Lexer.MULTIPLE)) { pd.setMultiple(true); } else if (currentTokenEquals(Lexer.COPY)) { pd.setOnParentVersion(OnParentVersionAction.COPY); } else if (currentTokenEquals(Lexer.VERSION)) { pd.setOnParentVersion(OnParentVersionAction.VERSION); } else if (currentTokenEquals(Lexer.INITIALIZE)) { pd.setOnParentVersion(OnParentVersionAction.INITIALIZE); } else if (currentTokenEquals(Lexer.COMPUTE)) { pd.setOnParentVersion(OnParentVersionAction.COMPUTE); } else if (currentTokenEquals(Lexer.IGNORE)) { pd.setOnParentVersion(OnParentVersionAction.IGNORE); } else if (currentTokenEquals(Lexer.ABORT)) { pd.setOnParentVersion(OnParentVersionAction.ABORT); } else if (currentTokenEquals(Lexer.NOFULLTEXT)) { pd.setFullTextSearchable(false); } else if (currentTokenEquals(Lexer.NOQUERYORDER)) { pd.setQueryOrderable(false); } else if (currentTokenEquals(Lexer.QUERYOPS)) { doPropertyQueryOperators(pd); } nextToken(); } } catch (RepositoryException e) { lexer.fail("Error setting property attribute of " + pd.getName() + " to " + currentToken, e); } } /** * processes the property query operators * * @param pd the property definition builder * @throws ParseException if an error occurs */ private void doPropertyQueryOperators(AbstractPropertyDefinitionBuilder<T> pd) throws ParseException { if (!currentTokenEquals(Lexer.QUERYOPS)) { return; } nextToken(); String[] ops = currentToken.split(","); List<String> queryOps = new LinkedList<String>(); for (String op : ops) { String s = op.trim(); if (s.equals(Lexer.QUEROPS_EQUAL)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO); } else if (s.equals(Lexer.QUEROPS_NOTEQUAL)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO); } else if (s.equals(Lexer.QUEROPS_LESSTHAN)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN); } else if (s.equals(Lexer.QUEROPS_LESSTHANOREQUAL)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO); } else if (s.equals(Lexer.QUEROPS_GREATERTHAN)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN); } else if (s.equals(Lexer.QUEROPS_GREATERTHANOREQUAL)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO); } else if (s.equals(Lexer.QUEROPS_LIKE)) { queryOps.add(QueryObjectModelConstants.JCR_OPERATOR_LIKE); } else { lexer.fail("'" + s + "' is not a valid query operator"); } } try { pd.setAvailableQueryOperators(queryOps.toArray(new String[queryOps.size()])); } catch (RepositoryException e) { lexer.fail("Error query operators for " + pd.getName() + " to " + currentToken, e); } } /** * processes the property default values * * @param pd property definition builder * @throws ParseException if an error during parsing occurs */ private void doPropertyDefaultValue(AbstractPropertyDefinitionBuilder<T> pd) throws ParseException { if (!currentTokenEquals(Lexer.DEFAULT)) { return; } do { nextToken(); try { pd.addDefaultValues(currentToken); } catch (RepositoryException e) { lexer.fail("Error adding default value for " + pd.getName() + " to " + currentToken, e); } nextToken(); } while (currentTokenEquals(Lexer.LIST_DELIMITER)); } /** * processes the property value constraints * * @param pd property definition builder * @throws ParseException if an error during parsing occurs */ private void doPropertyValueConstraints(AbstractPropertyDefinitionBuilder<T> pd) throws ParseException { if (!currentTokenEquals(Lexer.CONSTRAINT)) { return; } do { nextToken(); try { pd.addValueConstraint(currentToken); } catch (RepositoryException e) { lexer.fail("Error adding value constraint for " + pd.getName() + " to " + currentToken, e); } nextToken(); } while (currentTokenEquals(Lexer.LIST_DELIMITER)); } /** * processes the childnode definition * * @param nd node definition builder * @param ntd declaring nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doChildNodeDefinition(AbstractNodeDefinitionBuilder<T> nd, AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { try { nd.setName(currentToken); } catch (RepositoryException e) { lexer.fail("Invalid child node name '" + currentToken, e); } nextToken(); doChildNodeRequiredTypes(nd); doChildNodeDefaultType(nd); doChildNodeAttributes(nd, ntd); } /** * processes the childnode required types * * @param nd node definition builder * @throws ParseException if an error during parsing occurs */ private void doChildNodeRequiredTypes(AbstractNodeDefinitionBuilder<T> nd) throws ParseException { if (!currentTokenEquals(Lexer.BEGIN_TYPE)) { return; } do { nextToken(); try { nd.addRequiredPrimaryType(currentToken); } catch (RepositoryException e) { lexer.fail("Error setting required primary type of " + nd.getName() + " to " + currentToken, e); } nextToken(); } while (currentTokenEquals(Lexer.LIST_DELIMITER)); nextToken(); } /** * processes the childnode default types * * @param nd node definition builder * @throws ParseException if an error during parsing occurs */ private void doChildNodeDefaultType(AbstractNodeDefinitionBuilder<T> nd) throws ParseException { if (!currentTokenEquals(Lexer.DEFAULT)) { return; } nextToken(); try { nd.setDefaultPrimaryType(currentToken); } catch (RepositoryException e) { lexer.fail("Error setting default primary type of " + nd.getName() + " to " + currentToken, e); } nextToken(); } /** * processes the childnode attributes * * @param nd node definition builder * @param ntd declaring nodetype definition builder * @throws ParseException if an error during parsing occurs */ private void doChildNodeAttributes(AbstractNodeDefinitionBuilder<T> nd, AbstractNodeTypeDefinitionBuilder<T> ntd) throws ParseException { try { while (currentTokenEquals(Lexer.NODE_ATTRIBUTE)) { if (currentTokenEquals(Lexer.PRIMARY)) { ntd.setPrimaryItemName(nd.getName()); } else if (currentTokenEquals(Lexer.AUTOCREATED)) { nd.setAutoCreated(true); } else if (currentTokenEquals(Lexer.MANDATORY)) { nd.setMandatory(true); } else if (currentTokenEquals(Lexer.PROTECTED)) { nd.setProtected(true); } else if (currentTokenEquals(Lexer.SNS)) { nd.setAllowsSameNameSiblings(true); } else if (currentTokenEquals(Lexer.COPY)) { nd.setOnParentVersion(OnParentVersionAction.COPY); } else if (currentTokenEquals(Lexer.VERSION)) { nd.setOnParentVersion(OnParentVersionAction.VERSION); } else if (currentTokenEquals(Lexer.INITIALIZE)) { nd.setOnParentVersion(OnParentVersionAction.INITIALIZE); } else if (currentTokenEquals(Lexer.COMPUTE)) { nd.setOnParentVersion(OnParentVersionAction.COMPUTE); } else if (currentTokenEquals(Lexer.IGNORE)) { nd.setOnParentVersion(OnParentVersionAction.IGNORE); } else if (currentTokenEquals(Lexer.ABORT)) { nd.setOnParentVersion(OnParentVersionAction.ABORT); } nextToken(); } } catch (RepositoryException e) { lexer.fail("Error setting child node attribute of " + nd.getName() + " to " + currentToken, e); } } /** * Gets the next token from the underlying lexer. * * @throws ParseException if the lexer fails to get the next token. * @see Lexer#getNextToken() */ private void nextToken() throws ParseException { currentToken = lexer.getNextToken(); } /** * Checks if the {@link #currentToken} is semantically equal to the given * argument ignoring the case. * * @param s the tokens to compare with * @return <code>true</code> if equals; <code>false</code> otherwise. */ private boolean currentTokenEquals(String[] s) { for (String value : s) { if (currentToken.equalsIgnoreCase(value)) { return true; } } return false; } /** * Checks if the {@link #currentToken} is semantically equal to the given * argument. * * @param c the tokens to compare with * @return <code>true</code> if equals; <code>false</code> otherwise. */ private boolean currentTokenEquals(char c) { return currentToken.length() == 1 && currentToken.charAt(0) == c; } /** * Checks if the {@link #currentToken} is semantically equal to the given * argument. * * @param s the tokens to compare with * @return <code>true</code> if equals; <code>false</code> otherwise. */ private boolean currentTokenEquals(String s) { return currentToken.equals(s); } }