/* * 12/23/2012 * * JsonFoldParser.java - Fold parser for JSON. * * This library is distributed under a modified BSD license. See the included * RSyntaxTextArea.License.txt file for details. */ package org.fife.ui.rsyntaxtextarea.folding; import java.util.ArrayList; import java.util.List; import java.util.Stack; import javax.swing.text.BadLocationException; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Token; import org.fife.ui.rsyntaxtextarea.TokenTypes; /** * The fold parser for JSON. Objects (<code>"{ ... }</code>") and arrays * (<code>"[ ... ]"</code>) that span multiple lines are considered fold * regions. * * @author Robert Futrell * @version 1.0 */ public class JsonFoldParser implements FoldParser { private static final Object OBJECT_BLOCK = new Object(); private static final Object ARRAY_BLOCK = new Object(); /** * {@inheritDoc} */ public List<Fold> getFolds(RSyntaxTextArea textArea) { Stack<Object> blocks = new Stack<Object>(); List<Fold> folds = new ArrayList<Fold>(); Fold currentFold = null; int lineCount = textArea.getLineCount(); try { for (int line=0; line<lineCount; line++) { Token t = textArea.getTokenListForLine(line); while (t!=null && t.isPaintable()) { if (t.isLeftCurly()) { if (currentFold==null) { currentFold = new Fold(FoldType.CODE, textArea, t.getOffset()); folds.add(currentFold); } else { currentFold = currentFold.createChild(FoldType.CODE, t.getOffset()); } blocks.push(OBJECT_BLOCK); } else if (t.isRightCurly() && popOffTop(blocks, OBJECT_BLOCK)) { if (currentFold!=null) { currentFold.setEndOffset(t.getOffset()); Fold parentFold = currentFold.getParent(); //System.out.println("... Adding regular fold at " + t.offset + ", parent==" + parentFold); // Don't add fold markers for single-line blocks if (currentFold.isOnSingleLine()) { if (!currentFold.removeFromParent()) { folds.remove(folds.size()-1); } } currentFold = parentFold; } } else if (isLeftBracket(t)) { if (currentFold==null) { currentFold = new Fold(FoldType.CODE, textArea, t.getOffset()); folds.add(currentFold); } else { currentFold = currentFold.createChild(FoldType.CODE, t.getOffset()); } blocks.push(ARRAY_BLOCK); } else if (isRightBracket(t) && popOffTop(blocks, ARRAY_BLOCK)) { if (currentFold!=null) { currentFold.setEndOffset(t.getOffset()); Fold parentFold = currentFold.getParent(); //System.out.println("... Adding regular fold at " + t.offset + ", parent==" + parentFold); // Don't add fold markers for single-line blocks if (currentFold.isOnSingleLine()) { if (!currentFold.removeFromParent()) { folds.remove(folds.size()-1); } } currentFold = parentFold; } } t = t.getNextToken(); } } } catch (BadLocationException ble) { // Should never happen ble.printStackTrace(); } return folds; } /** * Returns whether a token is the left bracket token. * * @param t The token. * @return Whether the token is the left bracket token. * @see #isRightBracket(Token) */ private static final boolean isLeftBracket(Token t) { return t.getType()==TokenTypes.SEPARATOR && t.isSingleChar('['); } /** * Returns whether a token is the right bracket token. * * @param t The token. * @return Whether the token is the right bracket token. * @see #isLeftBracket(Token) */ private static final boolean isRightBracket(Token t) { return t.getType()==TokenTypes.SEPARATOR && t.isSingleChar(']'); } /** * If the specified value is on top of the stack, pop it off and return * <code>true</code>. Otherwise, return <code>false</code>. * * @param stack The stack. * @param value The value to check for. * @return Whether the value was found on top of the stack. */ private static final boolean popOffTop(Stack<Object> stack, Object value) { if (stack.size()>0 && stack.peek()==value) { stack.pop(); return true; } return false; } }