    001    /*
    002     * SideKickParsedData.java
    003     * :tabSize=8:indentSize=8:noTabs=false:
    004     * :folding=explicit:collapseFolds=1:
    005     *
    006     * Copyright (C) 2003, 2004 Slava Pestov
    007     *
    008     * This program is free software; you can redistribute it and/or
    009     * modify it under the terms of the GNU General Public License
    010     * as published by the Free Software Foundation; either version 2
    011     * of the License, or any later version.
    012     *
    013     * This program is distributed in the hope that it will be useful,
    014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
    015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    016     * GNU General Public License for more details.
    017     *
    018     * You should have received a copy of the GNU General Public License
    019     * along with this program; if not, write to the Free Software
    020     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    021     */
    022
    023    package sidekick;
    024
    025    //{{{ Imports
    026    import javax.swing.tree.*;
    027    import java.util.*;
    028    import org.gjt.sp.jedit.View;
    029    import org.gjt.sp.util.Log;
    030
    031    import javax.swing.text.Position;
    032    import sidekick.enhanced.SourceAsset;
    033
    034    //}}}
    035
    036    /**
    037     * Stores a buffer structure tree.
    038     *
    039     * @modified   $Id: SideKickParsedData.java,v 1.19 2006/06/04 15:03:49 ezust Exp $
    040     * @version    $Revision: 1.19 $
    041     * Plugins can extend this class to persist plugin-specific information.
    042     * For example, the XML plugin stores code completion-related structures using
    043     * a subclass.
    044     */
    045    public class SideKickParsedData
    046    {
    047            //{{{ getParsedData() method
    048            /**
    049             * Returns an instance of this class for the specified view.
    050             *
    051             * Note that this will only return a valid object after the
    052             * <code>SideKick.parse()</code> method is called.
    053             *
    054             * @param view The view.
    055             */
    056            public static SideKickParsedData getParsedData(View view)
    057            {
    058                    return (SideKickParsedData)view.getRootPane().getClientProperty(
    059                            SideKickPlugin.PARSED_DATA_PROPERTY);
    060            } //}}}
    061
    062            //{{{ setParsedData() method
    063            /**
    064             * Sets the instance of this class for the specified view.
    065             *
    066             * @param view The view.
    067             * @param data The instance.
    068             */
    069            public static void setParsedData(View view, SideKickParsedData data)
    070            {
    071                    Log.log(Log.DEBUG,SideKickParsedData.class,
    072                            "Setting parsed data of " + view + " to " + data);
    073                    view.getRootPane().putClientProperty(
    074                            SideKickPlugin.PARSED_DATA_PROPERTY,
    075                            data);
    076            } //}}}
    077
    078            public DefaultTreeModel tree;
    079
    080            /**
    081             * Plugin parsers should add nodes to the root node.
    082             */
    083            public DefaultMutableTreeNode root;
    084
    085
    086            //{{{ SideKickParsedData constructor
    087            /**
    088             * @param fileName The file name being parsed, used as the root of the
    089             * tree.
    090             */
    091            public SideKickParsedData(String fileName)
    092            {
    093                    // root node is missing an asset at this point, so make a
    094                    // temporary asset for it that covers the entire range of the
    095                    // buffer
    096                    SourceAsset asset = new SourceAsset(fileName, 0, new Position(){
    097                                    public int getOffset() {
    098                                            return 0;
    099                                    }
    100                    });
    101                    asset.setEnd(new Position(){
    102                                    public int getOffset() {
    103                                            return Integer.MAX_VALUE;
    104                                    }
    105                    });
    106                    root = new DefaultMutableTreeNode(asset);
    107                    tree = new DefaultTreeModel(root);
    108            } //}}}
    109
    110            //{{{ getTreePathForPosition() method
    111            /**
    112             * @param dot
    113             */
    114            public TreePath getTreePathForPosition(int dot)
    115            {
    116                    if(root.getChildCount() == 0) {
    117                            return null;
    118                    }
    119                    ArrayList _path = new ArrayList();
    120                    if (getTreePathForPosition(root, dot, _path)) {
    121                            _path.add(root);
    122                    }
    123                    if(_path.size() == 0)
    124                    {
    125                            // nothing found
    126                            Log.log(Log.DEBUG, this, "+++ nothing found");
    127                            return null;
    128                    }
    129                    else
    130                    {
    131                            Collections.reverse(_path);
    132                            return new TreePath(_path.toArray());
    133                    }
    134            } //}}}
    135
    136
    137         //{{{ getTreePathForPosition() method
    138         /* danson, updated so that I can pick the next node closest to the dot.  This helps
    139             with comments in java code -- since comments don't have a node associated
    140             with them, placing the cursor in a method comment, for example, would cause
    141             sidekick to highlight the class node rather than the associated method node.
    142             The following modifications will cause the node immediately following the
    143             cursor location to be highlighted in the tree. */
    144            /**
    145             * TODO: please document what these parameters mean.
    146
    147             * @param node
    148             * @param dot
    149             * @param path
    150             */
    151            protected boolean getTreePathForPosition( TreeNode node, int dot, List path )
    152            {
    153                    IAsset asset = getAsset( node );
    154                    if ( asset == null && !node.equals(root))
    155                    {
    156                            return false;
    157                    }
    158                    int childCount = node.getChildCount();
    159
    160                    // check if any of our children contain the caret
    161                    // hertzhaft: I put this test first so that trees that
    162                    // don't reflect the file order continue to work
    163                    for ( int i = childCount - 1; i >= 0; i-- )
    164                    {
    165                            TreeNode _node = node.getChildAt( i );
    166                            if ( getTreePathForPosition( _node, dot, path ) )
    167                            {
    168                                    path.add( _node );
    169                                    return true;
    170                            }
    171                    }
    172
    173                    // if here, the dot is not in any of our children
    174                    // check if the caret in inside this tag
    175                    if ( dot >= asset.getStart().getOffset() && dot <= asset.getEnd().getOffset() )
    176                    {
    177                            // find the next child
    178                            List children = new ArrayList();
    179                            for ( int i = 0; i < childCount; i ++ )
    180                            {
    181                                    children.add( node.getChildAt( i ) );
    182                            }
    183                            if ( children.size()  == 0 )
    184                            {
    185                                    return true;
    186                            }
    187
    188                            // sort child nodes by offset
    189                            Collections.sort( children, assetComparator );
    190
    191                            // check if the dot is before the first child, if so,
    192                            // we want the parent node, otherwise, clicking the mouse
    193                            // directly on the text for the parent node would cause
    194                            // the first child to get highlighted.  Really need a range
    195                            // the assets...
    196                            IAsset firstChild = getAsset((TreeNode)children.get(0));
    197                            if (firstChild == null || firstChild.getStart().getOffset() > dot)
    198                            {
    199                                    return true;
    200                            }
    201                            for ( Iterator it = children.iterator(); it.hasNext(); )
    202                            {
    203                                TreeNode tn = ( TreeNode ) it.next();
    204                                IAsset ias = getAsset(tn);
    205                                if ( ias == null || ias.getStart().getOffset() < dot )
    206                                {
    207                                        continue;
    208                                }
    209                                else
    210                                {
    211                                    if (canAddToPath(tn))
    212                                    {
    213                                            path.add( tn );
    214                                    }
    215                                    break;
    216                                }
    217                            }
    218                            return true;
    219                    }
    220                    else
    221                    {
    222                            return false;
    223                    }
    224            } //}}}
    225
    226            //{{{ canAddToPath() method
    227            /**
    228             * Subclasses can override this to handle special case nodes that may not
    229             * be suitable for adding to the path.  See JavaSideKick for an example.
    230             * @param node a TreeNode that is being considered for adding to a tree path.
    231             * @return true if it is okay to add the node.  This default implementation
    232             * always returns true.
    233             */
    234            protected boolean canAddToPath(TreeNode node) {
    235                    return true;
    236            } //}}}
    237
    238            //{{{ getAssetAtPosition() method
    239
    240            /**
    241             * @deprecated use {@link #getAssetAtOffset(int)} instead
    242             */
    243            public Asset getAssetAtPosition(int pos)
    244            {
    245                    return (Asset) getAssetAtOffset(pos);
    246            } //}}}
    247
    248            //{{{ getAssetAtOffset() method
    249            /**
    250             *
    251             * @param pos TODO: explain what pos means.
    252             */
    253            public IAsset getAssetAtOffset(int pos)
    254            {
    255                    TreePath path = getTreePathForPosition(pos);
    256                    if(path == null)
    257                            return null;
    258                    return (IAsset) ((DefaultMutableTreeNode)path
    259                            .getLastPathComponent()).getUserObject();
    260            } //}}}
    261
    262            //{{{ getAsset() method
    263            /**
    264             * Convenience method to get the IAsset from the user object in the node
    265             * @param node a DefaultMutableTreeNode.  Anything else will cause this method
    266             * to return null.
    267             * @return the IAsset contained in the user object in the node.
    268             */
    269            public IAsset getAsset( TreeNode node ) {
    270                   if ( !( node instanceof DefaultMutableTreeNode ) )
    271                   {
    272                           return null;
    273                   }
    274                   Object userObject = ( ( DefaultMutableTreeNode ) node ).getUserObject();
    275                   if ( !( userObject instanceof IAsset ) ) {
    276                           return null;
    277                   }
    278                   IAsset asset = ( IAsset ) userObject;
    279                   return asset;
    280            } //}}}
    281
    282            private Comparator assetComparator = new Comparator() {
    283                    public int compare( Object a, Object b ) {
    284                            IAsset ia = getAsset((TreeNode)a);
    285                            IAsset ib = getAsset((TreeNode)b);
    286                            /// should check for null IAssets here
    287
    288                            javax.swing.text.Position ap = ia.getStart();
    289                            javax.swing.text.Position bp = ib.getStart();
    290                            // check nulls
    291                            if (ap == null && bp == null) {
    292                                    return 0;
    293                            }
    294                            if (ap != null && bp == null) {
    295                                    return -1;
    296                            }
    297                            if (ap == null && bp != null) {
    298                                    return 1;
    299                            }
    300                            // neither are null, check offset
    301                            Integer ai = new Integer(ap.getOffset());
    302                            Integer bi = new Integer(bp.getOffset());
    303                            return ai.compareTo(bi);
    304                    }
    305            };
    306    }
    307