replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / softwrap / SoftWrapsStorage.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.editor.impl.softwrap;
17
18 import com.intellij.diagnostic.Dumpable;
19 import com.intellij.openapi.editor.SoftWrap;
20 import com.intellij.openapi.editor.TextChange;
21 import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
22 import com.intellij.util.containers.ContainerUtil;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29
30 /**
31  * Holds registered soft wraps and provides monitoring and management facilities for them.
32  * <p/>
33  * Not thread-safe.
34  *
35  * @author Denis Zhdanov
36  * @since Jun 29, 2010 3:04:20 PM
37  */
38 public class SoftWrapsStorage implements Dumpable {
39
40   private final List<SoftWrapImpl>        myWraps     = new ArrayList<>();
41   private final List<SoftWrapImpl>        myWrapsView = Collections.unmodifiableList(myWraps);
42   private final List<SoftWrapChangeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
43
44   /**
45    * @return    {@code true} if there is at least one soft wrap registered at the current storage; {@code false} otherwise
46    */
47   public boolean isEmpty() {
48     return myWraps.isEmpty();
49   }
50
51   @Nullable
52   public SoftWrap getSoftWrap(int offset) {
53     int i = getSoftWrapIndex(offset);
54     return i >= 0 ? myWraps.get(i) : null;
55   }
56
57   /**
58    * @return    view for registered soft wraps sorted by offset in ascending order if any; empty collection otherwise
59    */
60   @NotNull
61   public List<SoftWrapImpl> getSoftWraps() {
62     return myWrapsView;
63   }
64
65   /**
66    * Tries to find index of the target soft wrap stored at {@link #myWraps} collection. {@code 'Target'} soft wrap is the one
67    * that starts at the given offset.
68    *
69    * @param offset    target offset
70    * @return          index that conforms to {@link Collections#binarySearch(List, Object)} contract, i.e. non-negative returned
71    *                  index points to soft wrap that starts at the given offset; {@code '-(negative value) - 1'} points
72    *                  to position at {@link #myWraps} collection where soft wrap for the given index should be inserted
73    */
74   public int getSoftWrapIndex(int offset) {
75     int start = 0;
76     int end = myWraps.size() - 1;
77
78     // We use custom inline implementation of binary search here because profiling shows that standard Collections.binarySearch()
79     // is a bottleneck. The most probable reason is a big number of interface calls.
80     while (start <= end) {
81       int i = (start + end) >>> 1;
82       SoftWrap softWrap = myWraps.get(i);
83       int softWrapOffset = softWrap.getStart();
84       if (softWrapOffset > offset) {
85         end = i - 1;
86       }
87       else if (softWrapOffset < offset) {
88         start = i + 1;
89       }
90       else {
91         return i;
92       }
93     }
94     return -(start + 1);
95   }
96
97   /**
98    * Allows to answer how many soft wraps which {@link TextChange#getStart() start offsets} belong to given
99    * {@code [start; end]} interval are registered withing the current storage.
100    * 
101    * @param startOffset   target start offset (inclusive)
102    * @param endOffset     target end offset (inclusive)
103    * @return              number of soft wraps which {@link TextChange#getStart() start offsets} belong to the target range
104    */
105   public int getNumberOfSoftWrapsInRange(int startOffset, int endOffset) {
106     int startIndex = getSoftWrapIndex(startOffset);
107     if (startIndex < 0) {
108       startIndex = -startIndex - 1;
109     }
110
111     if (startIndex >= myWraps.size()) {
112       return 0;
113     }
114     int result = 0;
115     int endIndex = startIndex;
116     for (; endIndex < myWraps.size(); endIndex++) {
117       SoftWrap softWrap = myWraps.get(endIndex);
118       if (softWrap.getStart() > endOffset) {
119         break;
120       }
121       result++;
122     }
123     return result;
124   }
125   
126   /**
127    * Inserts given soft wrap to {@link #myWraps} collection at the given index.
128    *
129    * @param softWrap          soft wrap to store
130    * @return                  previous soft wrap object stored for the same offset if any; {@code null} otherwise
131    */
132   public void storeOrReplace(SoftWrapImpl softWrap) {
133     int i = getSoftWrapIndex(softWrap.getStart());
134     if (i >= 0) {
135       myWraps.set(i, softWrap);
136       return;
137     }
138
139     i = -i - 1;
140     myWraps.add(i, softWrap);
141   }
142
143   public void remove(SoftWrapImpl softWrap) {
144     if (myWraps.isEmpty()) return;
145     int i = myWraps.size() - 1; // expected use case is removing of last soft wrap, so we have a fast path here for that case
146     if (myWraps.get(i).getStart() != softWrap.getStart()) {
147       i = getSoftWrapIndex(softWrap.getStart());
148     }
149     if (i >= 0) {
150       myWraps.remove(i);
151     }
152   }
153
154   /**
155    * Removes soft wraps with offsets equal or larger than a given offset from storage.
156    * 
157    * @return soft wraps that were removed, ordered by offset
158    */
159   public List<SoftWrapImpl> removeStartingFrom(int offset) {
160     int startIndex = getSoftWrapIndex(offset);
161     if (startIndex < 0) {
162       startIndex = -startIndex - 1;
163     }
164
165     if (startIndex >= myWraps.size()) {
166       return Collections.emptyList();
167     }
168
169     List<SoftWrapImpl> tail = myWraps.subList(startIndex, myWraps.size());
170     List<SoftWrapImpl> result = new ArrayList<>(tail);
171     tail.clear();
172     return result;
173   }
174
175   /**
176    * Adds soft wraps to storage. They are supposed to be sorted by their offsets, and have offsets larger than offsets for soft wraps 
177    * existing in storage at the moment.
178    */
179   public void addAll(List<SoftWrapImpl> softWraps) {
180     myWraps.addAll(softWraps);
181   }
182
183   /**
184    * Removes all soft wraps registered at the current storage.
185    */
186   public void removeAll() {
187     myWraps.clear();
188     notifyListenersAboutChange();
189   }
190
191   /**
192    * Registers given listener within the current model
193    *
194    * @param listener    listener to register
195    * @return            {@code true} if given listener was not registered before; {@code false} otherwise
196    */
197   public boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener) {
198     return myListeners.add(listener);
199   }
200
201   public void notifyListenersAboutChange() {
202     for (SoftWrapChangeListener listener : myListeners) {
203       listener.softWrapsChanged();
204     }
205   }
206
207   @NotNull
208   @Override
209   public String dumpState() {
210     return myWraps.toString();
211   }
212 }