PLSQL/Folding/src/org/netbeans/modules/plsql/fold/NewPlsqlFoldManager.java
author Subhashini Sooriarachchi <subslk@netbeans.org>
Mon, 12 Aug 2013 11:26:54 +0530
changeset 464 e10b2e8563fc
parent 362 9a549f0d1c97
permissions -rw-r--r--
EADS-3749 encountering issues with the displaying of code in Developer Studio when code folding is enabled
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * If you wish your version of this file to be governed by only the CDDL
    28  * or only the GPL Version 2, indicate your decision by adding
    29  * "[Contributor] elects to include this software in this distribution
    30  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    31  * single choice of license, a recipient has the option to distribute
    32  * your version of this file under either the CDDL, the GPL Version 2 or
    33  * to extend the choice of license to its licensees as provided above.
    34  * However, if you add GPL Version 2 code and therefore, elected the GPL
    35  * Version 2 license, then the option applies only if the new code is
    36  * made subject to such option by the copyright holder.
    37  *
    38  * Contributor(s):
    39  *
    40  * Portions Copyrighted 2011 Sun Microsystems, Inc.
    41  */
    42 package org.netbeans.modules.plsql.fold;
    43 
    44 import java.util.ArrayList;
    45 import java.util.Collections;
    46 import java.util.Comparator;
    47 import java.util.List;
    48 import java.util.Observable;
    49 import java.util.Observer;
    50 import java.util.logging.Level;
    51 import java.util.logging.Logger;
    52 import javax.swing.event.DocumentEvent;
    53 import javax.swing.text.AbstractDocument;
    54 import javax.swing.text.BadLocationException;
    55 import javax.swing.text.Document;
    56 import org.netbeans.api.editor.fold.Fold;
    57 import org.netbeans.api.editor.fold.FoldHierarchy;
    58 import org.netbeans.api.editor.fold.FoldType;
    59 import org.netbeans.editor.BaseDocument;
    60 import org.netbeans.modules.plsql.lexer.PlsqlBlock;
    61 import org.netbeans.modules.plsql.lexer.PlsqlBlockFactory;
    62 import org.netbeans.modules.plsql.lexer.PlsqlBlockType;
    63 import org.netbeans.modules.plsqlsupport.options.OptionsUtilities;
    64 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
    65 import org.netbeans.spi.editor.fold.FoldManager;
    66 import org.netbeans.spi.editor.fold.FoldOperation;
    67 import org.openide.util.Exceptions;
    68 import org.openide.util.Lookup;
    69 import org.openide.util.RequestProcessor;
    70 
    71 /**
    72  *
    73  * @author chrlse
    74  */
    75 public class NewPlsqlFoldManager implements FoldManager, Runnable, Observer {
    76 
    77     private static final Logger LOG = Logger.getLogger(NewPlsqlFoldManager.class.getName());
    78     private static final RequestProcessor RP = new RequestProcessor(NewPlsqlFoldManager.class.getName(), 1, false, false);
    79     private static final int TASK_DELAY = 100;
    80     private final RequestProcessor.Task task = RP.create(this);
    81     private FoldOperation operation;
    82     private Document doc;
    83     // Note: FoldSearchObject need to be in a List, otherwise contains doesn't work. Seems HashSet keeps internal list of hash.
    84     private final List<FoldSearchObject> foldSearchObjects = new ArrayList<FoldSearchObject>();
    85     private List<Fold> removedFoldList = new ArrayList<Fold>(3);
    86     private boolean initial = true;
    87     private PlsqlBlockFactory blockFactory;
    88 
    89     @Override
    90     public void init(FoldOperation operation) {
    91         this.operation = operation;
    92         if (LOG.isLoggable(Level.FINE)) {
    93             LOG.log(Level.FINE, "Initialized: {0}", System.identityHashCode(this));
    94         }
    95     }
    96 
    97     private FoldOperation getOperation() {
    98         return operation;
    99     }
   100 
   101     @Override
   102     public void initFolds(FoldHierarchyTransaction transaction) {
   103         doc = getOperation().getHierarchy().getComponent().getDocument();
   104         blockFactory = getBlockFactory();
   105         if (blockFactory != null) {
   106             blockFactory.addObserver(this);
   107         }
   108         task.schedule(TASK_DELAY);
   109     }
   110 
   111     @Override
   112     public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
   113         if (LOG.isLoggable(Level.FINER)) {
   114             LOG.log(Level.FINER, "insertUpdate: {0}", System.identityHashCode(this));
   115         }
   116         processRemovedFolds(transaction);
   117     }
   118 
   119     @Override
   120     public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
   121         if (LOG.isLoggable(Level.FINER)) {
   122             LOG.log(Level.FINER, "removeUpdate: {0}", System.identityHashCode(this));
   123         }
   124         processRemovedFolds(transaction);
   125     }
   126 
   127     @Override
   128     public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
   129         if (LOG.isLoggable(Level.FINER)) {
   130             LOG.log(Level.FINER, "changeUpdate: {0}", System.identityHashCode(this));
   131         }
   132         processRemovedFolds(transaction);
   133     }
   134 
   135     @Override
   136     public void removeEmptyNotify(Fold emptyFold) {
   137         if (LOG.isLoggable(Level.FINER)) {
   138             LOG.log(Level.FINER, "removeEmptyNotify: {0}", System.identityHashCode(this));
   139         }
   140         removeFoldNotify(emptyFold);
   141     }
   142 
   143     @Override
   144     public void removeDamagedNotify(Fold damagedFold) {
   145         if (LOG.isLoggable(Level.FINER)) {
   146             LOG.log(Level.FINER, "removeDamagedNotify: {0}", System.identityHashCode(this));
   147         }
   148         removeFoldNotify(damagedFold);
   149     }
   150 
   151     @Override
   152     public void expandNotify(Fold expandedFold) {
   153     }
   154 
   155     @Override
   156     public void release() {
   157         if (LOG.isLoggable(Level.FINE)) {
   158             LOG.log(Level.FINE, "release: {0}", System.identityHashCode(this));
   159         }
   160     }
   161 
   162     @Override
   163     public void update(Observable o, Object arg) {
   164         if (LOG.isLoggable(Level.FINER)) {
   165             LOG.log(Level.FINER, "update: {0}", System.identityHashCode(this));
   166         }
   167         task.schedule(TASK_DELAY);
   168     }
   169 
   170     @Override
   171     public void run() {
   172         if (operation.isReleased()) {
   173             if (LOG.isLoggable(Level.FINE)) {
   174                 LOG.log(Level.FINE, "Update skipped, already released: {0}", System.identityHashCode(this));
   175             }
   176             return;
   177         }
   178         ((BaseDocument) doc).readLock();
   179         try {
   180             if (blockFactory != null) {
   181                 FoldHierarchy hierarchy = getOperation().getHierarchy();
   182 
   183                 hierarchy.lock();
   184                 try {
   185                     if (operation.isReleased()) {
   186                         if (LOG.isLoggable(Level.FINE)) {
   187                             LOG.log(Level.FINE, "Update skipped, already released: {0}", System.identityHashCode(this));
   188                         }
   189                         return;
   190                     }
   191                     if (LOG.isLoggable(Level.FINE)) {
   192                         LOG.log(Level.FINE, "Updating: {0}", System.identityHashCode(this));
   193                         LOG.log(Level.FINE, "blockFactory.getBlockHierarchy().size(): {0}", blockFactory.getBlockHierarchy().size());
   194                         LOG.log(Level.FINE, "blockFactory.getNewBlocks().size(): {0}", blockFactory.getNewBlocks().size());
   195                         LOG.log(Level.FINE, "blockFactory.getRemovedBlocks().size(): {0}", blockFactory.getRemovedBlocks().size());
   196                     }
   197                     FoldHierarchyTransaction transaction = getOperation().openTransaction();
   198                     try {
   199 
   200                         final Fold root = hierarchy.getRootFold();
   201 
   202                         if (initial) {
   203                             List<PlsqlBlock> blocks = blockFactory.getBlockHierarchy();
   204                             updateFolds(blocks, transaction, null);
   205                             //Add custom fold blocks
   206                             updateFolds(blockFactory.getCustomFolds(), transaction, null);
   207                             initial = false;
   208                         } else {
   209                             final List<FoldSearchObject> collapsedFolds = new ArrayList<FoldSearchObject>();
   210                             getCollapsedFolds(root, collapsedFolds);
   211                             final List<PlsqlBlock> oldBlocks = blockFactory.getRemovedBlocks();
   212 
   213                             //Remove non existing blocks   
   214                             if (!oldBlocks.isEmpty()) {
   215                                 final int childCount = root.getFoldCount();
   216                                 for (int i = (childCount - 1); i >= 0; i--) {
   217                                     final Fold child = root.getFold(i);
   218                                     removeWithChildren(child, oldBlocks, transaction);
   219                                 }
   220                             }
   221                             //Add new blocks to the hierarchy
   222                             List<PlsqlBlock> blocks = blockFactory.getNewBlocks();
   223                             updateFolds(blocks, transaction, collapsedFolds);
   224                             //Add custom fold blocks
   225                             updateFolds(blockFactory.getCustomFolds(), transaction, collapsedFolds);
   226                         }
   227                     } catch (BadLocationException ex) {
   228                         Exceptions.printStackTrace(ex);
   229                     } finally {
   230                         transaction.commit();
   231                     }
   232                 } finally {
   233                     hierarchy.unlock();
   234                 }
   235             }
   236         } finally {
   237             ((BaseDocument) doc).readUnlock();
   238         }
   239     }
   240 
   241     private void getCollapsedFolds(final Fold parent, final List<FoldSearchObject> foldInfoLst) {
   242         if (parent.isCollapsed()) {
   243             final FoldSearchObject tempInfo = new FoldSearchObject(parent.getStartOffset(), parent.getEndOffset(), parent.getType());
   244             foldInfoLst.add(tempInfo);
   245         }
   246         final int count = parent.getFoldCount();
   247         for (int i = 0; i < count; i++) {
   248             final Fold temp = parent.getFold(i);
   249             getCollapsedFolds(temp, foldInfoLst);
   250         }
   251     }
   252 
   253     /**
   254      * Remove fold with its children
   255      *
   256      * @param fold
   257      * @param blockHier
   258      * @param transaction
   259      * @return true if removed all the children
   260      */
   261     private void removeWithChildren(final Fold fold, final List<PlsqlBlock> blockHier, final FoldHierarchyTransaction transaction) {
   262 
   263         final int childCount = fold.getFoldCount();
   264         for (int i = (childCount - 1); i >= 0; i--) {
   265             final Fold child = fold.getFold(i);
   266             removeWithChildren(child, blockHier, transaction);
   267         }
   268 
   269         //If a custom fold remove
   270         if (fold.getType() == PlsqlFoldTypes.CUSTOM || checkExists(fold, blockHier)) {
   271             operation.removeFromHierarchy(fold, transaction);
   272             foldSearchObjects.remove(new FoldSearchObject(new FoldAdapter(fold)));
   273         }
   274     }
   275 
   276     /**
   277      * Method that will check whether given fold exists in block hier
   278      *
   279      * @param fold
   280      * @param blockHier
   281      * @return
   282      */
   283     private boolean checkExists(final Fold fold, final List<PlsqlBlock> blockHier) {
   284         final Comparator<PlsqlBlock> comparator = new Comparator<PlsqlBlock>() {
   285             @Override
   286             public int compare(final PlsqlBlock o1, final PlsqlBlock o2) {
   287                 Integer o1pos, o2pos;
   288                 if (o1.getStartOffset() > -1 && o2.getStartOffset() > -1) {
   289                     o1pos = Integer.valueOf(o1.getStartOffset());
   290                     o2pos = Integer.valueOf(o2.getStartOffset());
   291                 } else {
   292                     o1pos = Integer.valueOf(o1.getEndOffset());
   293                     o2pos = Integer.valueOf(o2.getEndOffset());
   294                 }
   295                 return o1pos.compareTo(o2pos);
   296             }
   297         };
   298         return checkExists(fold, blockHier, comparator);
   299     }
   300 
   301     /**
   302      * Method that will check whether given fold exists in block hier
   303      *
   304      * @param fold
   305      * @param blockHier
   306      * @param comparator
   307      * @return
   308      */
   309     private boolean checkExists(final Fold fold, final List<PlsqlBlock> blockHier, final Comparator<PlsqlBlock> comparator) {
   310         final int size = blockHier.size();
   311         Collections.sort(blockHier, comparator);
   312         if (size == 0) {
   313             return false;
   314         }
   315         //make sure that the fold isn't before the first block or after the last block in the hierarchy.
   316         if (fold.getStartOffset() > blockHier.get(size - 1).getEndOffset()
   317                 || fold.getEndOffset() < blockHier.get(0).getStartOffset()) {
   318             return false;
   319         }
   320         for (int i = 0; i < size; i++) {
   321             final PlsqlBlock block = blockHier.get(i);
   322             if (block.getStartOffset() <= fold.getStartOffset() && block.getEndOffset() >= fold.getEndOffset()) {
   323                 return true;
   324             }
   325             if (block.getPreviousStart() <= fold.getStartOffset() && block.getPreviousEnd() >= fold.getEndOffset()) {
   326                 return true;
   327             }
   328             if ((block.getEndOffset() == fold.getEndOffset() || fold.getEndOffset() == block.getPreviousEnd()
   329                     || block.getStartOffset() == fold.getStartOffset() || block.getPreviousStart() == fold.getStartOffset())
   330                     && (getFoldType(block.getType()).equals(fold.getType()))
   331                     && (getFoldDescription(block).equals(fold.getDescription()))) {
   332                 return true;
   333             }
   334 
   335             if (checkExists(fold, block.getChildBlocks(), comparator)) {
   336                 return true;
   337             }
   338         }
   339 
   340         return false;
   341     }
   342 
   343     private FoldType getFoldType(final PlsqlBlockType blockType) {
   344         switch (blockType) {
   345             case VIEW:
   346                 return PlsqlFoldTypes.VIEW;
   347             case TABLE_COMMENT:
   348                 return PlsqlFoldTypes.TABLECOMMENT;
   349             case COLUMN_COMMENT:
   350                 return PlsqlFoldTypes.COLUMNCOMMENT;
   351             case COMMENT:
   352                 return PlsqlFoldTypes.COMMENT;
   353             case PACKAGE:
   354                 return PlsqlFoldTypes.PACKAGE;
   355             case PACKAGE_BODY:
   356                 return PlsqlFoldTypes.PACKAGEBODY;
   357             case PROCEDURE_IMPL:
   358                 return PlsqlFoldTypes.PROCEDUREIMPL;
   359             case FUNCTION_IMPL:
   360                 return PlsqlFoldTypes.FUNCTIONIMPL;
   361             case PROCEDURE_DEF:
   362                 return PlsqlFoldTypes.PROCEDUREDEF;
   363             case FUNCTION_DEF:
   364                 return PlsqlFoldTypes.FUNCTIONDEF;
   365             case DECLARE_END:
   366                 return PlsqlFoldTypes.DECLAREEND;
   367             case BEGIN_END:
   368                 return PlsqlFoldTypes.BEGINEND;
   369             case TRIGGER:
   370                 return PlsqlFoldTypes.TRIGGER;
   371             case IF:
   372                 return PlsqlFoldTypes.IF;
   373             case CASE:
   374                 return PlsqlFoldTypes.CASE;
   375             case WHILE_LOOP:
   376                 return PlsqlFoldTypes.WHILELOOP;
   377             case FOR_LOOP:
   378                 return PlsqlFoldTypes.FORLOOP;
   379             case LOOP:
   380                 return PlsqlFoldTypes.LOOP;
   381             case CUSTOM_FOLD:
   382                 return PlsqlFoldTypes.CUSTOM;
   383             case STATEMENT:
   384                 return PlsqlFoldTypes.STATEMENT;
   385             case CURSOR:
   386                 return PlsqlFoldTypes.CURSOR;
   387             case JAVA_SOURCE:
   388                 return PlsqlFoldTypes.JAVASOURCE;
   389             default:
   390                 return null;
   391         }
   392     }
   393 
   394     /**
   395      * Method that will return the fold description given the Plsql block Used
   396      * when changing the descriptions only.
   397      *
   398      * @param block
   399      * @return
   400      */
   401     private String getFoldDescription(final PlsqlBlock block) {
   402         switch (block.getType()) {
   403             case VIEW:
   404                 return block.getPrefix() + "VIEW " + block.getName();
   405             case TABLE_COMMENT:
   406                 return "COMMENT ON TABLE " + block.getName();
   407             case COLUMN_COMMENT:
   408                 return "COLUMN COMMENTS ON TABLE " + block.getName();
   409             case COMMENT:
   410                 return block.getName();
   411             case PACKAGE:
   412                 return block.getPrefix() + "PACKAGE " + block.getName();
   413             case PACKAGE_BODY:
   414                 return block.getPrefix() + "PACKAGE BODY " + block.getName();
   415             case PROCEDURE_IMPL:
   416                 return block.getPrefix() + "PROCEDURE IMPLEMENTATION " + block.getName();
   417             case FUNCTION_IMPL:
   418                 return block.getPrefix() + "FUNCTION IMPLEMENTATION " + block.getName();
   419             case PROCEDURE_DEF:
   420                 return "PROCEDURE DEFINITION " + block.getName();
   421             case FUNCTION_DEF:
   422                 return "FUNCTION DEFINITION " + block.getName();
   423             case DECLARE_END:
   424                 return "DECLARE BLOCK";
   425             case BEGIN_END:
   426                 return "BEGIN BLOCK";
   427             case TRIGGER:
   428                 return block.getPrefix() + "TRIGGER " + block.getName();
   429             case IF:
   430                 return block.getName();
   431             case CASE:
   432                 return block.getName();
   433             case WHILE_LOOP:
   434                 return "WHILE " + block.getName();
   435             case FOR_LOOP:
   436                 return "FOR " + block.getName();
   437             case LOOP:
   438                 return "LOOP ";
   439             case CUSTOM_FOLD:
   440                 return block.getName();
   441             case STATEMENT:
   442                 return block.getPrefix() + block.getName();
   443             case CURSOR:
   444                 return "CURSOR " + block.getName();
   445             case JAVA_SOURCE:
   446                 return block.getPrefix() + "JAVA SOURCE";
   447             default:
   448                 return "";
   449         }
   450     }
   451 
   452     private void removeFoldNotify(Fold removedFold) {
   453         removedFoldList.add(removedFold);
   454     }
   455 
   456     private void processRemovedFolds(FoldHierarchyTransaction transaction) {
   457         for (Fold removedFold : removedFoldList) {
   458             operation.removeFromHierarchy(removedFold, transaction);
   459             if (LOG.isLoggable(Level.FINE)) {
   460                 LOG.log(Level.FINE, "Fold={0} removed={1}", new Object[]{removedFold, true});
   461             }
   462         }
   463         removedFoldList.clear();
   464     }
   465 
   466     private Document getDocument() {
   467         Object obj = null;
   468         for (int i = 0; i < 10; i++) {
   469             obj = getOperation().getHierarchy().getComponent().getDocument();
   470             if (obj instanceof AbstractDocument) {
   471                 return (Document) obj;
   472             }
   473             try {
   474                 Thread.currentThread().sleep(i);
   475             } catch (InterruptedException e) {
   476             }
   477         }
   478         throw new IllegalStateException("[PLSQLFolding] PLSQLFoldManager.getDocument() NOT returned AbstractDocument, but " + obj.getClass() + "!. This is caused by not yet resolved issue #49497."); //NOI18N
   479     }
   480 
   481     private void updateFolds(final List<PlsqlBlock> blockHier, final FoldHierarchyTransaction transaction, final List<FoldSearchObject> collapsedFolds) throws BadLocationException {
   482         final int count = blockHier.size();
   483         Document doc = getDocument();
   484         for (int i = 0; i < count; i++) {
   485             final PlsqlBlock block = blockHier.get(i);
   486             FoldType foldType = null;
   487             final PlsqlBlockType type = block.getType();
   488             String description = "";
   489 
   490             if (!(type == PlsqlBlockType.COMMENT && doc.getText(block.getStartOffset(), block.getEndOffset() - block.getStartOffset()).indexOf("\n") == -1)) { // check for single line comments
   491                 foldType = getFoldType(type);
   492                 description = getFoldDescription(block);
   493 
   494                 if (doc.getEndPosition().getOffset() >= block.getEndOffset()) {                    
   495                     operation.addToHierarchy(foldType, description, isCollapsed(block, foldType, collapsedFolds),
   496                             block.getStartOffset(), block.getEndOffset(), 0, 0, null, transaction);
   497                     //check for any child folds and add them also
   498                     updateFolds(block.getChildBlocks(), transaction, collapsedFolds);
   499                 }
   500             }
   501         }
   502     }
   503 
   504     /**
   505      * Method that will select and return the corresponding fold to parent from
   506      * oldRoot fold hierarchy
   507      *
   508      * @param parent
   509      * @param foldInfoLst
   510      * @return
   511      */
   512     private boolean isCollapsed(final PlsqlBlock block, final FoldType foldType, final List<FoldSearchObject> foldInfoLst) {
   513         if (OptionsUtilities.isPlSqlExpandFolds()) {
   514             return false;
   515         }
   516         if (foldInfoLst == null) {
   517             return foldType == PlsqlFoldTypes.COMMENT;
   518         }
   519         final int size = foldInfoLst.size();
   520         for (int i = 0; i < size; i++) {
   521             final FoldSearchObject temp = foldInfoLst.get(i);
   522 
   523             if ((temp.getFoldType() == foldType)
   524                     && (temp.getStartOffset() == block.getPreviousStart())
   525                     && (temp.getEndOffset() == block.getPreviousEnd())) {
   526                 return true;
   527             }
   528         }
   529 
   530         return false;
   531     }
   532 
   533     /**
   534      * Method that will return the relevant block factory
   535      *
   536      * @return
   537      */
   538     private PlsqlBlockFactory getBlockFactory() {
   539         final Object obj = doc.getProperty(Document.StreamDescriptionProperty);
   540         if (obj instanceof Lookup.Provider) {
   541             return ((Lookup.Provider) obj).getLookup().lookup(PlsqlBlockFactory.class);
   542         }
   543         return null;
   544     }
   545 }