/** * 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.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Kevin Lindsey */ public class MatcherMap { /** * @author Kevin Lindsey */ private class CharacterClassMap { private List<ITextMatcher> _matchers; private ITextMatcher[] _cache; private boolean _sealed; /** * CharacterClassMap */ public CharacterClassMap() { } /** * addAllMatchers * * @param matchers */ public void addAllMatchers(List<ITextMatcher> matchers) { if (this._sealed == false && matchers != null) { this._matchers.addAll(matchers); } } /** * addMatcher * * @param matcher */ public void addMatcher(ITextMatcher matcher) { if (this._sealed == false) { // make sure we have a container for our matchers if (this._matchers == null) { this._matchers = new ArrayList<ITextMatcher>(); } if (this._matchers.contains(matcher) == false) { this._matchers.add(matcher); // clear cache this._cache = null; _hasBuiltinCharacterClass = true; } } } /** * getMatcherList * * @return List or null */ public List<ITextMatcher> getMatcherList() { return this._matchers; } /** * getMatchers * * @return IMatcher[] or null */ public ITextMatcher[] getMatchers() { if (this._cache == null && this._matchers != null) { this._cache = this._matchers.toArray(new ITextMatcher[this._matchers.size()]); Arrays.sort(this._cache); } return this._cache; } /** * hasMatcher * * @return boolean */ public boolean hasMatchers() { return ((this._matchers != null && this._matchers.size() > 0) || (this._cache != null && this._cache.length > 0)); } /** * setSealed */ public void setSealed() { if (this._sealed == false) { this._sealed = true; // fill cache this._cache = null; this.getMatchers(); // release array list this._matchers = null; } } } private static final ITextMatcher[] NO_MATCHERS = new ITextMatcher[0]; private Map<Character,List<ITextMatcher>> _map; private Map<Character,ITextMatcher[]> _sealedMap; private CharacterClassMap _uncategorizedMatchers; private boolean _hasBuiltinCharacterClass; private boolean _sealed; private CharacterClassMap _digitMatchers; private CharacterClassMap _letterMatchers; private CharacterClassMap _whitespaceMatchers; private CharacterClassMap _negatedDigitMatchers; private CharacterClassMap _negatedLetterMatchers; private CharacterClassMap _negatedWhitespaceMatchers; /** * MatcherMap */ public MatcherMap() { } /** * addMatcher * * @param c * @param matcher */ public void addCharacterMatcher(char c, ITextMatcher matcher) { if (this._sealed == false) { if (matcher != null) { // create map for character, if needed if (this._map == null) { this._map = new HashMap<Character,List<ITextMatcher>>(); } // create list for character, if needed if (this._map.containsKey(c) == false) { this._map.put(c, new ArrayList<ITextMatcher>()); } // grab list for character List<ITextMatcher> list = this._map.get(c); // add this matcher if it's not already in the list if (list.contains(matcher) == false) { list.add(matcher); } } } } /** * addDigitMatcher * * @param matcher */ public void addDigitMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._digitMatchers == null) { this._digitMatchers = new CharacterClassMap(); } this._digitMatchers.addMatcher(matcher); } } /** * addLetterMatcher * * @param matcher */ public void addLetterMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._letterMatchers == null) { this._letterMatchers = new CharacterClassMap(); } this._letterMatchers.addMatcher(matcher); } } /** * addNegatedCharacterMatcher * * @param c * @param matcher */ public void addNegatedCharacterMatcher(char c, ITextMatcher matcher) { // TODO: not implemented. Setting all matchers as uncategorized this.addUncategorizedMatcher(matcher); } /** * addNegatedDigitMatcher * * @param matcher */ public void addNegatedDigitMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._negatedDigitMatchers == null) { this._negatedDigitMatchers = new CharacterClassMap(); } this._negatedDigitMatchers.addMatcher(matcher); } } /** * addNegatedLetterMatcher * * @param matcher */ public void addNegatedLetterMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._negatedLetterMatchers == null) { this._negatedLetterMatchers = new CharacterClassMap(); } this._negatedLetterMatchers.addMatcher(matcher); } } /** * addNegatedWhitespaceMatcher * * @param matcher */ public void addNegatedWhitespaceMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._negatedWhitespaceMatchers == null) { this._negatedWhitespaceMatchers = new CharacterClassMap(); } this._negatedWhitespaceMatchers.addMatcher(matcher); } } /** * addUncategorizedMatcher * * @param matcher */ public void addUncategorizedMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._uncategorizedMatchers == null) { this._uncategorizedMatchers = new CharacterClassMap(); } this._uncategorizedMatchers.addMatcher(matcher); } } /** * addWhitespaceMatcher * * @param matcher */ public void addWhitespaceMatcher(ITextMatcher matcher) { if (this._sealed == false) { if (this._whitespaceMatchers == null) { this._whitespaceMatchers = new CharacterClassMap(); } this._whitespaceMatchers.addMatcher(matcher); } } /** * appendMatchers * * @param matchers * @param map */ private void appendToList(boolean isInCharacterClass, CharacterClassMap map, List<ITextMatcher> matchers) { if (isInCharacterClass && map != null) { matchers.addAll(map.getMatcherList()); } } /** * appendToMap * * @param isInCharacteClass * @param matcher * @param map */ private void appendToMap(boolean isInCharacteClass, List<ITextMatcher> matcher, CharacterClassMap map) { if (isInCharacteClass && map != null) { map.addAllMatchers(matcher); } } /** * getMatchers * * @param c * @return IMatcher[] */ public ITextMatcher[] getMatchers(char c) { if (this._sealed == false) { throw new IllegalStateException(Messages.MatcherMap_Call_SetSeal_Before_GetMatchers); } ITextMatcher[] result = null; if (this._sealedMap != null) { result = this._sealedMap.get(c); } if (result == null) { result = NO_MATCHERS; if (this._hasBuiltinCharacterClass) { if (this._letterMatchers != null && Character.isLetter(c)) { result = this._letterMatchers.getMatchers(); } else if (this._digitMatchers != null && Character.isDigit(c)) { result = this._digitMatchers.getMatchers(); } else if (this._whitespaceMatchers != null && Character.isWhitespace(c)) { result = this._whitespaceMatchers.getMatchers(); } else if (this._negatedDigitMatchers != null && Character.isDigit(c) == false) { result = this._negatedDigitMatchers.getMatchers(); } else if (this._negatedLetterMatchers != null && Character.isLetter(c) == false) { result = this._negatedLetterMatchers.getMatchers(); } else if (this._negatedWhitespaceMatchers != null && Character.isWhitespace(c) == false) { result = this._negatedWhitespaceMatchers.getMatchers(); } else if (this._uncategorizedMatchers != null) { result = this._uncategorizedMatchers.getMatchers(); } } else if (this._uncategorizedMatchers != null) { result = this._uncategorizedMatchers.getMatchers(); } } return result; } /** * getUncategorizedMatchers * * @return IMatcher[] */ public ITextMatcher[] getUncategorizedMatchers() { if (this._sealed == false) { throw new IllegalStateException(Messages.MatcherMap_Call_SetSeal_Before_GetMatchers); } ITextMatcher[] result = NO_MATCHERS; if (this._uncategorizedMatchers != null) { result = this._uncategorizedMatchers.getMatchers(); } return result; } /** * hasUncategorizedMatchers * * @return Returns true if this map included matchers that couldn't determine their first characters */ public boolean hasUncategorizedMatchers() { boolean result = false; if (this._uncategorizedMatchers != null) { result = this._uncategorizedMatchers.hasMatchers(); } return result; } /** * sealCharacterClass * * @param map */ private void sealCharacterClass(CharacterClassMap map) { if (map != null) { map.setSealed(); } } /** * setSealed */ public void setSealed() { // join all special character classes to our map as needed if (this._map != null) { Set<Character> keys = this._map.keySet(); Iterator<Character> iter = keys.iterator(); // create new sealed map container this._sealedMap = new HashMap<Character,ITextMatcher[]>(); while (iter.hasNext()) { char c = iter.next(); List<ITextMatcher> matcherList = this._map.get(c); // add all character classes that include this character this.appendToList(Character.isLetter(c), this._letterMatchers, matcherList); this.appendToList(Character.isDigit(c), this._digitMatchers, matcherList); this.appendToList(Character.isWhitespace(c), this._whitespaceMatchers, matcherList); this.appendToList(Character.isLetter(c) == false, this._negatedLetterMatchers, matcherList); this.appendToList(Character.isDigit(c) == false, this._negatedDigitMatchers, matcherList); this.appendToList(Character.isWhitespace(c) == false, this._negatedWhitespaceMatchers, matcherList); // add all uncategorized matchers since they could possibly match this.appendToList(true, this._uncategorizedMatchers, matcherList); // create matcher array ITextMatcher[] matchers = matcherList.toArray(new ITextMatcher[matcherList.size()]); // make sure we try matchers in document order Arrays.sort(matchers); // place array into sealed map this._sealedMap.put(c, matchers); } // release map this._map = null; } // add all uncategorized matchers to characters classes since they could possibly match if (this.hasUncategorizedMatchers()) { List<ITextMatcher> matcherList = this._uncategorizedMatchers.getMatcherList(); this.appendToMap(true, matcherList, this._letterMatchers); this.appendToMap(true, matcherList, this._digitMatchers); this.appendToMap(true, matcherList, this._whitespaceMatchers); this.appendToMap(true, matcherList, this._negatedLetterMatchers); this.appendToMap(true, matcherList, this._negatedDigitMatchers); this.appendToMap(true, matcherList, this._negatedWhitespaceMatchers); } // seal uncategorized matchers this.sealCharacterClass(this._uncategorizedMatchers); // seal all character classes this.sealCharacterClass(this._letterMatchers); this.sealCharacterClass(this._digitMatchers); this.sealCharacterClass(this._whitespaceMatchers); this.sealCharacterClass(this._negatedDigitMatchers); this.sealCharacterClass(this._negatedLetterMatchers); this.sealCharacterClass(this._negatedWhitespaceMatchers); // set sealed flag this._sealed = true; } }