/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package ca.weblite.netbeans.mirah.typinghooks; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.text.Document; import org.netbeans.api.lexer.Language; import org.netbeans.api.lexer.TokenChange; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenHierarchyEvent; import org.netbeans.api.lexer.TokenHierarchyEventType; import org.netbeans.api.lexer.TokenHierarchyListener; import org.netbeans.api.lexer.TokenId; import org.netbeans.api.lexer.TokenSequence; /** * Token balance computes balance of certain tokens - mainly braces. */ class TokenBalance implements TokenHierarchyListener { public static <T extends TokenId> TokenBalance get(Document doc) { TokenBalance tb = (TokenBalance)doc.getProperty(TokenBalance.class); if (tb == null) { tb = new TokenBalance(doc); doc.putProperty(TokenBalance.class, tb); } return tb; } private final Document doc; private final Map<Language<?>,LanguageHandler<?>> lang2handler; private boolean scanDone; private TokenBalance(Document doc) { this.doc = doc; lang2handler = new HashMap<Language<?>, LanguageHandler<?>>(); TokenHierarchy hi = TokenHierarchy.get(doc); hi.addTokenHierarchyListener(this); } public boolean isTracked(Language<?> language) { return (handler(language, false) != null); } public <T extends TokenId> void addTokenPair(Language<T> language, T left, T right) { synchronized (lang2handler) { handler(language, true).addTokenPair(left, right); scanDone = false; } } public void tokenHierarchyChanged(TokenHierarchyEvent evt) { synchronized (lang2handler) { if (evt.type() == TokenHierarchyEventType.ACTIVITY || evt.type() == TokenHierarchyEventType.REBUILD) { scanDone = false; } else { if (scanDone) { // Only update if the full scan was already done for (LanguageHandler<?> handler : lang2handler.values()) { handler.handleEvent(evt); } } } } } /** * Get balance for the given left id. * * @param left left-id * @return balance value above zero means more lefts than rights and vice versa. * Returns Integer.MAX_VALUE if the particular id is not tracked (or is tracked * as non-left id e.g. '[' would return balance but ']' would return Integer.MAX_VALUE). */ public <T extends TokenId> int balance(Language<T> language, T left) { synchronized (lang2handler) { checkScanDone(); LanguageHandler<T> handler = handler(language, false); return (handler != null) ? handler.balance(left) : Integer.MAX_VALUE; } } private <T extends TokenId> LanguageHandler<T> handler(Language<T> language, boolean forceCreation) { // Should always be called under lang2handler sync section @SuppressWarnings("unchecked") LanguageHandler<T> handler = (LanguageHandler<T>) lang2handler.get(language); if (handler == null && forceCreation) { handler = new LanguageHandler<T>(language); lang2handler.put(language, handler); } return handler; } private void checkScanDone() { List<LanguageHandler<?>> vals = new ArrayList(); synchronized (lang2handler) { vals.addAll(lang2handler.values()); } if (!scanDone) { TokenHierarchy hi = TokenHierarchy.get(doc); for (LanguageHandler<?> handler : vals) { handler.scan(hi); } scanDone = true; } } private static final class LanguageHandler<T extends TokenId> { private final Language<T> language; private final Map<T, TokenIdPair<T>> id2Pair; LanguageHandler(Language<T> language) { this.language = language; id2Pair = new HashMap<T, TokenIdPair<T>>(); } public final Language<T> language() { return language; } public void addTokenPair(T left, T right) { TokenIdPair<T> pair = new TokenIdPair<T>(left, right); synchronized (id2Pair) { id2Pair.put(left, pair); id2Pair.put(right, pair); } } public void scan(TokenHierarchy hi) { for (TokenIdPair pair : id2Pair.values()) { pair.balance = 0; } // Clear balances first TokenSequence<?> ts = hi.tokenSequence(); if (ts != null) { processTokenSequence(ts, ts.tokenCount(), true, +1); } } public void processTokenSequence(TokenSequence<?> ts, int tokenCount, boolean checkEmbedded, int diff) { while (--tokenCount >= 0) { boolean moved = ts.moveNext(); assert (moved); if (ts.language() == language) { T id = (T)ts.token().id(); TokenIdPair pair = id2Pair.get(id); if (pair != null) { pair.updateBalance(id, diff); } } if (checkEmbedded) { TokenSequence<?> embeddedTS = ts.embedded(); if (embeddedTS != null) processTokenSequence(embeddedTS, embeddedTS.tokenCount(), true, diff); } } } public void handleEvent(TokenHierarchyEvent evt) { for (TokenChange<T> tokenChange : collectTokenChanges(evt.tokenChange(), new ArrayList<TokenChange<T>>())) { if (tokenChange.removedTokenCount() > 0) { processTokenSequence(tokenChange.removedTokenSequence(), tokenChange.removedTokenCount(), false, -1); } if (tokenChange.addedTokenCount() > 0) { processTokenSequence(tokenChange.currentTokenSequence(), tokenChange.addedTokenCount(), false, +1); } } } public int balance(T left) { TokenIdPair pair = id2Pair.get(left); if ( pair == null ){ return 0; } return (pair.left == left) ? pair.balance : Integer.MAX_VALUE; } private List<TokenChange<T>> collectTokenChanges(TokenChange<?> change, List<TokenChange<T>> changes) { if (change.language() == language) changes.add((TokenChange<T>)change); for (int i = 0; i < change.embeddedChangeCount(); i++) { collectTokenChanges(change.embeddedChange(i), changes); } return changes; } } private static final class TokenIdPair<T extends TokenId> { T left; T right; int balance; public TokenIdPair(T left, T right) { this.left = left; this.right = right; } public void updateBalance(T id, int diff) { if (id == left) { balance += diff; } else { assert (id == right); balance -= diff; } } } }