/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.lexer.matcher; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.aptana.ide.lexer.IToken; import com.aptana.ide.lexer.ITokenList; import com.aptana.ide.lexer.matcher.model.CategoryGroupElement; import com.aptana.ide.lexer.matcher.model.MatcherElement; import com.aptana.ide.lexer.matcher.model.TokenGroupElement; import com.aptana.xml.INode; /** * @author Kevin Lindsey */ public abstract class AbstractTextMatcher extends MatcherElement implements ITextMatcher { private static final String EMPTY_STRING = ""; //$NON-NLS-1$ private IToken _matchedToken; private String _name; private Map<String,String> _valuesByName; private List<NameValueChangeListener> _nameValueChangeListeners; /** * this matcher's token type */ protected IToken token; /** * AbstractMatcher */ public AbstractTextMatcher() { this.addChildTypes(); } /** * accept * * @param source * @param startingOffset * @param endingOffset * @param token */ protected void accept(char[] source, int startingOffset, int endingOffset, IToken token) { if (token != null) { this.setMatchedToken(token); } if (this._name != null && this._name.length() > 0) { // get matched text String text = new String(source, startingOffset, endingOffset - startingOffset); this.setNameValue(this._name, text); } } /** * addChildTypes */ public abstract void addChildTypes(); /** * addFirstCharacters * * @param map */ public void addFirstCharacters(MatcherMap map) { this.addFirstCharacters(map, this); } /** * @see com.aptana.ide.lexer.matcher.ITextMatcher#addFirstCharacters(com.aptana.ide.lexer.matcher.MatcherMap, * com.aptana.ide.lexer.matcher.ITextMatcher) */ public void addFirstCharacters(MatcherMap map, ITextMatcher target) { map.addUncategorizedMatcher(target); } /** * addNameValueChangeListener * * @param listener */ public void addNameValueChangeListener(NameValueChangeListener listener) { if (listener != null) { AbstractTextMatcher top = this.getExpressionRoot(); if (top._nameValueChangeListeners == null) { top._nameValueChangeListeners = new ArrayList<NameValueChangeListener>(); } if (top._nameValueChangeListeners.contains(listener) == false) { top._nameValueChangeListeners.add(listener); } } } /** * @see com.aptana.ide.lexer.matcher.ITextMatcher#canMatchNothing() */ public boolean canMatchNothing() { return false; } /** * @see com.aptana.ide.lexer.matcher.model.MatcherElement#createToken(com.aptana.ide.lexer.ITokenList) */ protected void createToken(ITokenList tokenList) { String type = this.getType(); if (type != null && type.length() > 0) { String group = this.getGroup(); String category = this.getCategory(); String newGroup = this.getSwitchTo(); // create lexer token IToken token = tokenList.createToken(); // set group, category, type, and new lexer group token.setLexerGroup(group); token.setCategory(category); token.setType(type); token.setNewLexerGroup(newGroup); try { // add to token list tokenList.add(token); // set token as matcher's return value this.token = token; } catch (IllegalArgumentException e) { this.getDocument().sendError(e.getMessage(), this); } } } /** * fireNameValueChange */ private void fireNameValueChange(String name, String oldValue, String newValue) { AbstractTextMatcher top = this.getExpressionRoot(); if (top._nameValueChangeListeners != null) { for (int i = 0; i < top._nameValueChangeListeners.size(); i++) { NameValueChangeListener listener = top._nameValueChangeListeners.get(i); listener.nameValueChanged(name, oldValue, newValue); } } } /** * getExpressionRoot * * @return AbstractMatcher */ protected AbstractTextMatcher getExpressionRoot() { // find top-most parent AbstractTextMatcher result = this; while (result != null) { INode parent = result.getParent(); if (parent instanceof AbstractTextMatcher) { result = (AbstractTextMatcher) parent; } else { break; } } return result; } /** * @see com.aptana.ide.lexer.matcher.ITextMatcher#getMatchedToken() */ public IToken getMatchedToken() { return this._matchedToken; } /** * getName * * @return String or null */ public String getName() { return this._name; } /** * getNameValue * * @param name * @return String */ protected String getNameValue(String name) { String result = EMPTY_STRING; AbstractTextMatcher top = this.getExpressionRoot(); if (top._valuesByName != null && top._valuesByName.containsKey(name)) { result = top._valuesByName.get(name); } return result; } /** * getToken * * @return IToken or null */ public IToken getToken() { return this.token; } /** * @see com.aptana.ide.lexer.matcher.ITextMatcher#match(char[], int, int) */ public abstract int match(char[] source, int offset, int eofOffset); /** * removeNameValueChangeListener * * @param listener */ public void removeNameValueChangeListener(NameValueChangeListener listener) { AbstractTextMatcher top = this.getExpressionRoot(); if (top._nameValueChangeListeners != null) { top._nameValueChangeListeners.remove(listener); } } /** * setMatchedToken * * @param token */ protected void setMatchedToken(IToken token) { this._matchedToken = token; // propagate up the tree INode parent = this.getParent(); if (parent != null && parent instanceof AbstractTextMatcher) { ((AbstractTextMatcher) parent).setMatchedToken(token); } } /** * setName * * @param name */ public void setName(String name) { this._name = name; } /** * setNameValue * * @param name * @param value */ protected void setNameValue(String name, String value) { // find top-most parent AbstractTextMatcher top = this.getExpressionRoot(); // make sure we have a hash to populate if (top._valuesByName == null) { top._valuesByName = new HashMap<String,String>(); } // locate the old value String oldValue = null; if (top._valuesByName.containsKey(name)) { oldValue = top._valuesByName.get(name); } // set the new value top._valuesByName.put(name, value); // fire a change event this.fireNameValueChange(name, oldValue, value); } /** * @see com.aptana.xml.NodeBase#setParent(com.aptana.xml.INode) */ protected void setParent(INode parent) { super.setParent(parent); // transfer name/value change listeners to expression root, if we're not the root if (this._nameValueChangeListeners != null) { AbstractTextMatcher top = this.getExpressionRoot(); if (top != this) { while (this._nameValueChangeListeners.size() > 0) { // get listener NameValueChangeListener listener = this._nameValueChangeListeners.get(0); // add to root top.addNameValueChangeListener(listener); // NOTE: we can't use removeNameValueChangeListener below as that finds the expression root and // deletes from there. That would delete the listener we just added to the parent and cause an // infinite loop // remove from self this._nameValueChangeListeners.remove(listener); } // clear list this._nameValueChangeListeners = null; } } } /** * @see java.lang.Object#toString() */ public String toString() { Class<?> thisClass = this.getClass(); String fullName = thisClass.getName(); String thisPackage = thisClass.getPackage().getName(); String result; if (thisPackage != null && thisPackage.length() > 0) { result = fullName.substring(thisPackage.length() + 1); } else { result = fullName; } return result; } /** * @see com.aptana.ide.lexer.matcher.model.MatcherElement#validateLocal() */ protected void validateLocal() { INode parent = this.getParent(); String type = this.getType(); if (parent instanceof CategoryGroupElement) { // NOTE: CategoryGroup already tests for category, so we don't need to test for that here if (this.getTypeDefinedInSubtree() == false) { if (this.getChildCount() > 0) { this.getDocument().sendError(Messages.AbstractMatcher_No_Type_On_Self_Or_Descendants, this); } else { this.getDocument().sendError(Messages.AbstractMatcher_No_Type, this); } } } else if (parent instanceof TokenGroupElement) { String category = this.getCategory(); if (type == null || type.length() == 0) { this.getDocument().sendError(Messages.AbstractMatcher_No_Type, this); } if (category == null || category.length() == 0) { this.getDocument().sendError(Messages.AbstractMatcher_No_Category, this); } } else { if (type == null || type.length() == 0) { // see if we're in a category group boolean insideCategoryGroup = false; while (parent != null) { if (parent instanceof CategoryGroupElement) { insideCategoryGroup = true; break; } else { parent = parent.getParent(); } } // make sure we have a category and a group if we're not in a category group if (insideCategoryGroup == false) { String category = this.getCategory(); if (category == null || category.length() == 0) { this.getDocument().sendError(Messages.AbstractMatcher_No_Category, this); } } } } } /** * wrapChildrenInAndElement */ protected void wrapChildrenInAndElement() { // wrap multiple children in an <and> element if (this.getChildCount() > 1) { AndMatcher and = new AndMatcher(); while (this.getChildCount() > 0) { and.appendChild(this.getChild(0)); } this.appendChild(and); } } }