Revision 43838 trunk/org.gvsig.desktop/org.gvsig.desktop.compat.cdc/org.gvsig.fmap.dal/org.gvsig.fmap.dal.impl/src/main/java/org/gvsig/fmap/dal/feature/impl/FeatureManager.java
FeatureManager.java | ||
---|---|---|
32 | 32 |
import java.util.NoSuchElementException; |
33 | 33 |
|
34 | 34 |
import org.gvsig.fmap.dal.exception.DataException; |
35 |
import org.gvsig.fmap.dal.feature.EditableFeature; |
|
36 | 35 |
import org.gvsig.fmap.dal.feature.Feature; |
37 |
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor; |
|
38 | 36 |
import org.gvsig.fmap.dal.feature.FeatureReference; |
39 | 37 |
import org.gvsig.fmap.dal.feature.FeatureStore; |
40 | 38 |
import org.gvsig.fmap.dal.feature.FeatureType; |
41 | 39 |
import org.gvsig.fmap.dal.feature.impl.expansionadapter.ExpansionAdapter; |
42 |
import org.gvsig.fmap.dal.feature.spi.DefaultFeatureProvider; |
|
43 |
import org.gvsig.fmap.dal.feature.spi.FeatureProvider; |
|
44 | 40 |
|
45 | 41 |
|
46 |
/** |
|
47 |
* DOCUMENT ME! |
|
48 |
* |
|
49 |
* @author Vicente Caballero Navarro |
|
50 |
*/ |
|
51 | 42 |
public class FeatureManager { |
52 |
private ExpansionAdapter expansionAdapter; |
|
53 |
private Collection deleted = new LinkedHashSet();//<FeatureID> |
|
54 |
private int deltaSize=0; |
|
55 |
private Map added = new LinkedHashMap(); |
|
56 |
private Map addedAndDeleted = new LinkedHashMap(); |
|
57 |
private HashMap modifiedFromOriginal=new HashMap(); |
|
58 | 43 |
|
59 |
public FeatureManager(ExpansionAdapter expansionAdapter){ |
|
60 |
this.expansionAdapter=expansionAdapter; |
|
44 |
private int deltaSize = 0; |
|
45 |
private final ExpansionAdapter expansionAdapter; |
|
46 |
private final Collection<FeatureReference> deleted = new LinkedHashSet(); |
|
47 |
private final Map<FeatureReference, Integer> added = new LinkedHashMap(); |
|
48 |
private final Map<FeatureReference, Integer> addedAndDeleted = new LinkedHashMap(); |
|
49 |
private final Map<FeatureReference, Integer> modifiedFromOriginal = new HashMap(); |
|
50 |
|
|
51 |
public FeatureManager(ExpansionAdapter expansionAdapter) { |
|
52 |
this.expansionAdapter = expansionAdapter; |
|
61 | 53 |
} |
62 | 54 |
|
63 |
/** |
|
64 |
* Deletes feature from this manager. |
|
65 |
* @param id |
|
66 |
* @return The deleted feature or null if the |
|
67 |
* feature had not been edited or previously added in the editing session |
|
68 |
*/ |
|
55 |
/** |
|
56 |
* Deletes feature from this manager. |
|
57 |
* |
|
58 |
* @param id |
|
59 |
* @return The deleted feature or null if the feature had not been edited or |
|
60 |
* previously added in the editing session |
|
61 |
*/ |
|
69 | 62 |
public Feature delete(FeatureReference id) { |
70 | 63 |
deleted.add(id); |
71 |
Integer num = (Integer) added.remove(id);
|
|
72 |
Feature feature=null;
|
|
73 |
if (num == null || num.intValue() == -1) {
|
|
74 |
num = (Integer) modifiedFromOriginal.remove(id);
|
|
64 |
Integer num = added.remove(id);
|
|
65 |
Feature feature = null;
|
|
66 |
if (num == null || num == -1) { |
|
67 |
num = modifiedFromOriginal.remove(id); |
|
75 | 68 |
if (num != null) { |
76 |
feature = (Feature) expansionAdapter.getObject(num.intValue());
|
|
69 |
feature = (Feature) expansionAdapter.getObject(num); |
|
77 | 70 |
} |
78 | 71 |
// if num is null here, method returns null |
79 |
}else{
|
|
80 |
feature = (Feature) expansionAdapter.getObject(num.intValue());
|
|
81 |
addedAndDeleted.put(id, num);
|
|
82 |
}
|
|
72 |
} else {
|
|
73 |
feature = (Feature) expansionAdapter.getObject(num);
|
|
74 |
addedAndDeleted.put(id, num);
|
|
75 |
}
|
|
83 | 76 |
deltaSize--; |
84 | 77 |
return feature; |
85 | 78 |
} |
86 | 79 |
|
87 |
/** |
|
88 |
* DOCUMENT ME! |
|
89 |
* |
|
90 |
* @param feature DOCUMENT ME! |
|
91 |
*/ |
|
92 | 80 |
public void add(Feature feature) { |
93 | 81 |
int pos = expansionAdapter.addObject(feature); |
94 |
added.put(feature.getReference(),new Integer(pos));
|
|
82 |
added.put(feature.getReference(), pos);
|
|
95 | 83 |
deleted.remove(feature.getReference()); |
96 | 84 |
deltaSize++; |
97 | 85 |
} |
98 | 86 |
|
99 |
/** |
|
100 |
* DOCUMENT ME! |
|
101 |
* @param id DOCUMENT ME! |
|
102 |
*/ |
|
103 | 87 |
public Feature deleteLastFeature() { |
104 | 88 |
expansionAdapter.deleteLastObject(); |
105 |
Feature feature=(Feature)expansionAdapter.getObject(expansionAdapter.getSize()-1);
|
|
89 |
Feature feature = (Feature) expansionAdapter.getObject(expansionAdapter.getSize() - 1);
|
|
106 | 90 |
added.remove(feature.getReference()); |
107 | 91 |
modifiedFromOriginal.remove(feature.getReference()); |
108 | 92 |
deltaSize--; |
... | ... | |
112 | 96 |
/** |
113 | 97 |
* Returns a Feature of the default type. |
114 | 98 |
* |
115 |
* @param id |
|
116 |
* the feature reference |
|
117 |
* @param store |
|
118 |
* the store to get the feature from |
|
99 |
* @param id the feature reference |
|
100 |
* @param store the store to get the feature from |
|
119 | 101 |
* @return a Feature with the given reference |
120 |
* @throws DataException |
|
121 |
* if there is an error getting the Feature |
|
102 |
* @throws DataException if there is an error getting the Feature |
|
122 | 103 |
*/ |
123 | 104 |
public Feature get(FeatureReference id, FeatureStore store) |
124 | 105 |
throws DataException { |
... | ... | |
128 | 109 |
/** |
129 | 110 |
* Returns a Feature of the given type. |
130 | 111 |
* |
131 |
* @param id |
|
132 |
* the feature reference |
|
133 |
* @param store |
|
134 |
* the store to get the feature from |
|
135 |
* @param featureType |
|
136 |
* the type of the feature to return |
|
112 |
* @param id the feature reference |
|
113 |
* @param store the store to get the feature from |
|
114 |
* @param featureType the type of the feature to return |
|
137 | 115 |
* @return a Feature with the given reference |
138 |
* @throws DataException |
|
139 |
* if there is an error getting the Feature |
|
116 |
* @throws DataException if there is an error getting the Feature |
|
140 | 117 |
*/ |
141 | 118 |
public Feature get(FeatureReference id, FeatureStore store, |
142 |
FeatureType featureType) throws DataException { |
|
143 |
// FIXME: y si el featuretype que paso esta modificado. |
|
144 |
// Deberia buscarlo en el featuretypemanager ? |
|
145 |
// |
|
146 |
// Si no existe feature con ese id... ? retorna null ? |
|
147 |
// en EditedDefaultIterator se hace uso de ese comportamiento. |
|
148 |
// |
|
149 |
Integer intNum = ((Integer) added.get(id)); |
|
150 |
if (intNum == null){ |
|
151 |
intNum =((Integer) modifiedFromOriginal.get(id)); |
|
152 |
if (intNum == null){ |
|
153 |
//If the feature has been added and deleted |
|
154 |
intNum = (Integer)addedAndDeleted.get(id); |
|
155 |
if (intNum == null){ |
|
156 |
return null; |
|
157 |
} |
|
158 |
} |
|
159 |
} |
|
160 |
int num = intNum.intValue(); |
|
161 |
if (num==-1) { |
|
162 |
return null; |
|
163 |
} |
|
164 |
Feature feature=(Feature)expansionAdapter.getObject(num); |
|
165 |
if (featureType== null){ |
|
166 |
featureType = store.getDefaultFeatureType(); |
|
119 |
FeatureType featureType) throws DataException { |
|
120 |
// FIXME: y si el featuretype que paso esta modificado. |
|
121 |
// Deberia buscarlo en el featuretypemanager ? |
|
122 |
// |
|
123 |
// Si no existe feature con ese id... ? retorna null ? |
|
124 |
// en EditedDefaultIterator se hace uso de ese comportamiento. |
|
125 |
// |
|
126 |
Integer intNum = (added.get(id)); |
|
127 |
if (intNum == null) { |
|
128 |
intNum = (modifiedFromOriginal.get(id)); |
|
129 |
if (intNum == null) { |
|
130 |
//If the feature has been added and deleted |
|
131 |
intNum = addedAndDeleted.get(id); |
|
132 |
if (intNum == null) { |
|
133 |
return null; |
|
134 |
} |
|
135 |
} |
|
167 | 136 |
} |
137 |
int num = intNum; |
|
138 |
if (num == -1) { |
|
139 |
return null; |
|
140 |
} |
|
141 |
Feature feature = (Feature) expansionAdapter.getObject(num); |
|
142 |
if (featureType == null) { |
|
143 |
featureType = store.getDefaultFeatureType(); |
|
144 |
} |
|
168 | 145 |
return new DefaultFeature(featureType, feature); |
169 | 146 |
} |
170 | 147 |
|
171 |
/** |
|
172 |
* DOCUMENT ME! |
|
173 |
* |
|
174 |
* @param feature DOCUMENT ME! |
|
175 |
* @param oldFeature DOCUMENT ME! |
|
176 |
*/ |
|
177 | 148 |
public int update(Feature feature, Feature oldFeature) { |
178 |
int oldNum=-1;
|
|
149 |
int oldNum = -1;
|
|
179 | 150 |
int num = expansionAdapter.addObject(feature); |
180 |
FeatureReference id=feature.getReference();
|
|
181 |
if (added.containsKey(id)){ |
|
182 |
oldNum=((Integer)added.get(id)).intValue();
|
|
183 |
added.put(id,new Integer(num));
|
|
184 |
}else{
|
|
185 |
if (modifiedFromOriginal.get(id)!=null) {
|
|
186 |
oldNum=((Integer)modifiedFromOriginal.get(id)).intValue();
|
|
187 |
}
|
|
188 |
modifiedFromOriginal.put(id,new Integer(num));
|
|
151 |
FeatureReference id = feature.getReference();
|
|
152 |
if (added.containsKey(id)) {
|
|
153 |
oldNum = (added.get(id));
|
|
154 |
added.put(id, num);
|
|
155 |
} else {
|
|
156 |
if (modifiedFromOriginal.get(id) != null) {
|
|
157 |
oldNum = (modifiedFromOriginal.get(id));
|
|
158 |
}
|
|
159 |
modifiedFromOriginal.put(id, num);
|
|
189 | 160 |
} |
190 | 161 |
return oldNum; |
191 | 162 |
} |
192 | 163 |
|
193 |
/** |
|
194 |
* DOCUMENT ME! |
|
195 |
* |
|
196 |
* @param id DOCUMENT ME! |
|
197 |
*/ |
|
198 | 164 |
public void restore(FeatureReference id) { |
199 | 165 |
deleted.remove(id); |
200 | 166 |
deltaSize++; |
201 | 167 |
} |
202 |
public void restore(FeatureReference id,int num){ |
|
203 |
if (added.containsKey(id)){ |
|
204 |
added.put(id,new Integer(num)); |
|
205 |
}else{ |
|
206 |
modifiedFromOriginal.put(id,new Integer(num)); |
|
168 |
|
|
169 |
public void restore(FeatureReference id, int num) { |
|
170 |
if (added.containsKey(id)) { |
|
171 |
added.put(id, num); |
|
172 |
} else { |
|
173 |
modifiedFromOriginal.put(id, num); |
|
207 | 174 |
} |
208 | 175 |
} |
209 | 176 |
|
210 |
|
|
211 |
public boolean isDeleted(Feature feature){ |
|
212 |
return deleted.contains(feature.getReference()); |
|
177 |
public boolean isDeleted(Feature feature) { |
|
178 |
return deleted.contains(feature.getReference()); |
|
213 | 179 |
} |
214 | 180 |
|
215 | 181 |
public boolean isDeleted(FeatureReference featureID) { |
216 |
return deleted.contains(featureID);
|
|
217 |
}
|
|
182 |
return deleted.contains(featureID);
|
|
183 |
}
|
|
218 | 184 |
|
219 |
public void clear() {
|
|
220 |
added.clear();
|
|
221 |
modifiedFromOriginal.clear();
|
|
222 |
expansionAdapter.close();
|
|
223 |
deleted.clear();//<FeatureID>
|
|
224 |
addedAndDeleted.clear();
|
|
225 |
deltaSize=0;
|
|
226 |
}
|
|
185 |
public void clear() {
|
|
186 |
added.clear();
|
|
187 |
modifiedFromOriginal.clear();
|
|
188 |
expansionAdapter.close();
|
|
189 |
deleted.clear();
|
|
190 |
addedAndDeleted.clear();
|
|
191 |
deltaSize = 0;
|
|
192 |
}
|
|
227 | 193 |
|
194 |
public boolean hasChanges() { |
|
195 |
return added.size() > 0 || modifiedFromOriginal.size() > 0 || deleted.size() > 0; |
|
196 |
} |
|
228 | 197 |
|
229 |
public boolean hasChanges() { |
|
230 |
return added.size()>0 || modifiedFromOriginal.size() > 0 || deleted.size() > 0; |
|
231 |
} |
|
198 |
public Iterator getDeleted() { |
|
199 |
return new DeletedIterator(); |
|
232 | 200 |
|
233 |
public Iterator getDeleted() { |
|
234 |
return new DeletedIterator(); |
|
201 |
} |
|
235 | 202 |
|
236 |
}
|
|
203 |
private class DeletedIterator implements Iterator {
|
|
237 | 204 |
|
238 |
class DeletedIterator implements Iterator { |
|
239 |
private Boolean hasnext = null; |
|
240 |
private Iterator iter; |
|
241 |
private DefaultFeatureReference obj; |
|
205 |
private Boolean hasnext = null; |
|
206 |
private final Iterator iter; |
|
207 |
private DefaultFeatureReference obj; |
|
242 | 208 |
|
243 |
public DeletedIterator(){
|
|
244 |
iter = deleted.iterator();
|
|
245 |
}
|
|
209 |
public DeletedIterator() {
|
|
210 |
iter = deleted.iterator();
|
|
211 |
}
|
|
246 | 212 |
|
247 |
public boolean hasNext() { |
|
248 |
if (hasnext != null) { |
|
249 |
return hasnext.booleanValue(); |
|
250 |
} |
|
251 |
hasnext = Boolean.FALSE; |
|
252 |
while (iter.hasNext()) { |
|
253 |
obj = (DefaultFeatureReference) iter.next(); |
|
254 |
if (obj.isNewFeature()) { |
|
255 |
continue; |
|
256 |
} |
|
257 |
hasnext = Boolean.TRUE; |
|
258 |
break; |
|
259 |
} |
|
260 |
return hasnext.booleanValue(); |
|
261 |
} |
|
213 |
@Override |
|
214 |
public boolean hasNext() { |
|
215 |
if (hasnext != null) { |
|
216 |
return hasnext; |
|
217 |
} |
|
218 |
hasnext = false; |
|
219 |
while (iter.hasNext()) { |
|
220 |
obj = (DefaultFeatureReference) iter.next(); |
|
221 |
if (obj.isNewFeature()) { |
|
222 |
continue; |
|
223 |
} |
|
224 |
hasnext = false; |
|
225 |
break; |
|
226 |
} |
|
227 |
return hasnext; |
|
228 |
} |
|
262 | 229 |
|
263 |
public Object next() { |
|
264 |
if (!hasNext()) { |
|
265 |
throw new NoSuchElementException(); |
|
266 |
} |
|
230 |
@Override |
|
231 |
public Object next() { |
|
232 |
if (!hasNext()) { |
|
233 |
throw new NoSuchElementException(); |
|
234 |
} |
|
235 |
hasnext = null; |
|
236 |
return obj; |
|
237 |
} |
|
267 | 238 |
|
268 |
hasnext = null; |
|
269 |
return obj; |
|
270 |
} |
|
239 |
@Override |
|
240 |
public void remove() { |
|
241 |
throw new UnsupportedOperationException(); |
|
242 |
} |
|
271 | 243 |
|
272 |
public void remove() { |
|
273 |
throw new UnsupportedOperationException(); |
|
244 |
} |
|
274 | 245 |
|
275 |
} |
|
246 |
public Iterator getInserted() { |
|
247 |
return new InsertedIterator(); |
|
248 |
} |
|
276 | 249 |
|
277 |
}
|
|
250 |
private class InsertedIterator implements Iterator {
|
|
278 | 251 |
|
279 |
public Iterator getInserted() {
|
|
280 |
return new InsertedIterator();
|
|
281 |
}
|
|
252 |
private final Iterator addedIter;
|
|
253 |
private DefaultFeature obj;
|
|
254 |
private Boolean hasnext = null;
|
|
282 | 255 |
|
283 |
class InsertedIterator implements Iterator { |
|
256 |
public InsertedIterator() { |
|
257 |
addedIter = added.values().iterator(); |
|
258 |
} |
|
284 | 259 |
|
285 |
private Iterator addedIter; |
|
286 |
private DefaultFeature obj; |
|
287 |
private Boolean hasnext = null; |
|
260 |
@Override |
|
261 |
public boolean hasNext() { |
|
262 |
if (hasnext != null) { |
|
263 |
return hasnext; |
|
264 |
} |
|
265 |
hasnext = false; |
|
266 |
int pos; |
|
267 |
while (addedIter.hasNext()) { |
|
268 |
pos = ((Integer) addedIter.next()); |
|
269 |
obj = (DefaultFeature) expansionAdapter.getObject(pos); |
|
270 |
if (!deleted.contains(obj.getReference())) { |
|
271 |
hasnext = true; |
|
272 |
break; |
|
273 |
} |
|
274 |
} |
|
275 |
return hasnext; |
|
276 |
} |
|
288 | 277 |
|
289 |
public InsertedIterator(){ |
|
290 |
addedIter = added.values().iterator(); |
|
291 |
} |
|
278 |
@Override |
|
279 |
public Object next() { |
|
280 |
if (!hasNext()) { |
|
281 |
throw new NoSuchElementException(); |
|
282 |
} |
|
283 |
hasnext = null; |
|
284 |
return obj.getData(); |
|
285 |
} |
|
292 | 286 |
|
293 |
public boolean hasNext() { |
|
294 |
if (hasnext != null) { |
|
295 |
return hasnext.booleanValue(); |
|
296 |
} |
|
297 |
hasnext = Boolean.FALSE; |
|
298 |
int pos; |
|
299 |
while (addedIter.hasNext()) { |
|
300 |
pos = ((Integer) addedIter.next()).intValue(); |
|
301 |
obj = (DefaultFeature) expansionAdapter.getObject(pos); |
|
302 |
if (!deleted.contains(obj.getReference())) { |
|
303 |
hasnext = Boolean.TRUE; |
|
304 |
break; |
|
305 |
} |
|
306 |
} |
|
307 |
return hasnext.booleanValue(); |
|
308 |
} |
|
287 |
@Override |
|
288 |
public void remove() { |
|
289 |
addedIter.remove(); |
|
290 |
} |
|
309 | 291 |
|
310 |
public Object next() { |
|
311 |
if (!hasNext()) { |
|
312 |
throw new NoSuchElementException(); |
|
313 |
} |
|
314 |
hasnext = null; |
|
315 |
return obj.getData(); |
|
316 |
} |
|
292 |
} |
|
317 | 293 |
|
318 |
public void remove() {
|
|
319 |
addedIter.remove();
|
|
320 |
}
|
|
294 |
public Iterator getUpdated() {
|
|
295 |
return new UpdatedIterator();
|
|
296 |
}
|
|
321 | 297 |
|
322 |
} |
|
323 |
public Iterator getUpdated() { |
|
324 |
return new UpdatedIterator(); |
|
325 |
} |
|
326 |
class UpdatedIterator implements Iterator{ |
|
327 |
private Boolean hasnext = null; |
|
328 |
private Iterator iter; |
|
329 |
private DefaultFeature obj; |
|
330 |
private int pos; |
|
298 |
private class UpdatedIterator implements Iterator { |
|
331 | 299 |
|
332 |
public UpdatedIterator() {
|
|
333 |
iter = expansionAdapter.iterator();
|
|
334 |
pos = -1;
|
|
335 |
}
|
|
300 |
private Boolean hasnext = null;
|
|
301 |
private final Iterator iter;
|
|
302 |
private DefaultFeature obj;
|
|
303 |
private int pos;
|
|
336 | 304 |
|
337 |
public boolean hasNext() { |
|
338 |
if (hasnext != null) { |
|
339 |
return hasnext.booleanValue(); |
|
340 |
} |
|
341 |
hasnext = Boolean.FALSE; |
|
342 |
while (iter.hasNext()) { |
|
343 |
pos++; |
|
344 |
obj = (DefaultFeature) iter.next(); |
|
345 |
if (deleted.contains(obj.getReference())){ |
|
346 |
continue; |
|
347 |
}else if (!modifiedFromOriginal.containsValue(new Integer(pos))){ |
|
348 |
continue; |
|
349 |
}else { |
|
350 |
hasnext = Boolean.TRUE; |
|
351 |
break; |
|
352 |
} |
|
353 |
} |
|
354 |
return hasnext.booleanValue(); |
|
355 |
} |
|
305 |
public UpdatedIterator() { |
|
306 |
iter = expansionAdapter.iterator(); |
|
307 |
pos = -1; |
|
308 |
} |
|
356 | 309 |
|
357 |
public Object next() { |
|
358 |
if (!hasNext()) { |
|
359 |
throw new NoSuchElementException(); |
|
360 |
} |
|
361 |
hasnext = null; |
|
362 |
return obj.getData(); |
|
363 |
} |
|
310 |
@Override |
|
311 |
public boolean hasNext() { |
|
312 |
if (hasnext != null) { |
|
313 |
return hasnext; |
|
314 |
} |
|
315 |
hasnext = false; |
|
316 |
while (iter.hasNext()) { |
|
317 |
pos++; |
|
318 |
obj = (DefaultFeature) iter.next(); |
|
319 |
if (deleted.contains(obj.getReference())) { |
|
320 |
continue; |
|
321 |
} else if (!modifiedFromOriginal.containsValue(pos)) { |
|
322 |
continue; |
|
323 |
} else { |
|
324 |
hasnext = true; |
|
325 |
break; |
|
326 |
} |
|
327 |
} |
|
328 |
return hasnext; |
|
329 |
} |
|
364 | 330 |
|
365 |
public void remove() { |
|
366 |
throw new UnsupportedOperationException(); |
|
331 |
@Override |
|
332 |
public Object next() { |
|
333 |
if (!hasNext()) { |
|
334 |
throw new NoSuchElementException(); |
|
335 |
} |
|
336 |
hasnext = null; |
|
337 |
return obj.getData(); |
|
338 |
} |
|
367 | 339 |
|
368 |
} |
|
369 |
} |
|
340 |
@Override |
|
341 |
public void remove() { |
|
342 |
throw new UnsupportedOperationException(); |
|
370 | 343 |
|
371 |
public boolean hasNews() { |
|
372 |
return !added.isEmpty(); |
|
373 |
} |
|
344 |
} |
|
345 |
} |
|
374 | 346 |
|
375 |
public long getDeltaSize() {
|
|
376 |
return deltaSize;
|
|
377 |
}
|
|
347 |
public boolean hasNews() {
|
|
348 |
return !added.isEmpty();
|
|
349 |
}
|
|
378 | 350 |
|
351 |
public long getDeltaSize() { |
|
352 |
return deltaSize; |
|
353 |
} |
|
354 |
|
|
379 | 355 |
/** |
380 | 356 |
* Indicates if any operation has comprimised the selected features. |
357 |
* |
|
381 | 358 |
* @return |
382 | 359 |
*/ |
383 | 360 |
public boolean isSelectionCompromised() { |
384 | 361 |
//Only deleted features can change order, as added features are added at the end. |
385 |
return deleted.size()>0;
|
|
362 |
return deleted.size() > 0;
|
|
386 | 363 |
} |
387 | 364 |
} |
Also available in: Unified diff