Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgMergeCommand.java @ 705:b4242b7e7dfe
Merge command: implement conflict resolution alternatives
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 15 Aug 2013 18:43:50 +0200 |
parents | 7743a9c10bfa |
children | cd5c87d96315 |
comparison
equal
deleted
inserted
replaced
704:7743a9c10bfa | 705:b4242b7e7dfe |
---|---|
16 */ | 16 */ |
17 package org.tmatesoft.hg.core; | 17 package org.tmatesoft.hg.core; |
18 | 18 |
19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; | 19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; |
20 | 20 |
21 import java.io.File; | |
22 import java.io.FileInputStream; | |
23 import java.io.IOException; | |
21 import java.io.InputStream; | 24 import java.io.InputStream; |
22 | 25 |
23 import org.tmatesoft.hg.internal.Callback; | 26 import org.tmatesoft.hg.internal.Callback; |
24 import org.tmatesoft.hg.internal.CsetParamKeeper; | 27 import org.tmatesoft.hg.internal.CsetParamKeeper; |
28 import org.tmatesoft.hg.internal.DirstateBuilder; | |
29 import org.tmatesoft.hg.internal.DirstateReader; | |
25 import org.tmatesoft.hg.internal.Experimental; | 30 import org.tmatesoft.hg.internal.Experimental; |
31 import org.tmatesoft.hg.internal.FileUtils; | |
32 import org.tmatesoft.hg.internal.Internals; | |
26 import org.tmatesoft.hg.internal.ManifestRevision; | 33 import org.tmatesoft.hg.internal.ManifestRevision; |
34 import org.tmatesoft.hg.internal.MergeStateBuilder; | |
27 import org.tmatesoft.hg.internal.Pool; | 35 import org.tmatesoft.hg.internal.Pool; |
36 import org.tmatesoft.hg.internal.Transaction; | |
37 import org.tmatesoft.hg.internal.WorkingDirFileWriter; | |
28 import org.tmatesoft.hg.repo.HgChangelog; | 38 import org.tmatesoft.hg.repo.HgChangelog; |
29 import org.tmatesoft.hg.repo.HgParentChildMap; | 39 import org.tmatesoft.hg.repo.HgParentChildMap; |
30 import org.tmatesoft.hg.repo.HgRepository; | 40 import org.tmatesoft.hg.repo.HgRepository; |
31 import org.tmatesoft.hg.repo.HgRepositoryLock; | 41 import org.tmatesoft.hg.repo.HgRepositoryLock; |
32 import org.tmatesoft.hg.repo.HgRevisionMap; | 42 import org.tmatesoft.hg.repo.HgRevisionMap; |
60 public HgMergeCommand changeset(int revisionIndex) throws HgBadArgumentException { | 70 public HgMergeCommand changeset(int revisionIndex) throws HgBadArgumentException { |
61 initHeadsAndAncestor(new CsetParamKeeper(repo).set(revisionIndex).get()); | 71 initHeadsAndAncestor(new CsetParamKeeper(repo).set(revisionIndex).get()); |
62 return this; | 72 return this; |
63 } | 73 } |
64 | 74 |
65 public void execute(Mediator mediator) throws HgCallbackTargetException, HgRepositoryLockException, HgLibraryFailureException, CancelledException { | 75 public void execute(Mediator mediator) throws HgCallbackTargetException, HgRepositoryLockException, HgIOException, HgLibraryFailureException, CancelledException { |
66 if (firstCset == BAD_REVISION || secondCset == BAD_REVISION || ancestorCset == BAD_REVISION) { | 76 if (firstCset == BAD_REVISION || secondCset == BAD_REVISION || ancestorCset == BAD_REVISION) { |
67 throw new IllegalArgumentException("Merge heads and their ancestors are not initialized"); | 77 throw new IllegalArgumentException("Merge heads and their ancestors are not initialized"); |
68 } | 78 } |
69 final HgRepositoryLock wdLock = repo.getWorkingDirLock(); | 79 final HgRepositoryLock wdLock = repo.getWorkingDirLock(); |
70 wdLock.acquire(); | 80 wdLock.acquire(); |
71 try { | 81 try { |
72 Pool<Nodeid> cacheRevs = new Pool<Nodeid>(); | 82 Pool<Nodeid> cacheRevs = new Pool<Nodeid>(); |
73 Pool<Path> cacheFiles = new Pool<Path>(); | 83 Pool<Path> cacheFiles = new Pool<Path>(); |
84 | |
85 Internals implRepo = Internals.getInstance(repo); | |
86 final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo); | |
87 dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource(repo.getSessionContext().getPathFactory(), cacheFiles))); | |
88 final HgChangelog clog = repo.getChangelog(); | |
89 dirstateBuilder.parents(clog.getRevision(firstCset), clog.getRevision(secondCset)); | |
90 // | |
91 MergeStateBuilder mergeStateBuilder = new MergeStateBuilder(implRepo); | |
92 | |
74 ManifestRevision m1, m2, ma; | 93 ManifestRevision m1, m2, ma; |
75 m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); | 94 m1 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, firstCset); |
76 m2 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, secondCset); | 95 m2 = new ManifestRevision(cacheRevs, cacheFiles).init(repo, secondCset); |
77 ma = new ManifestRevision(cacheRevs, cacheFiles).init(repo, ancestorCset); | 96 ma = new ManifestRevision(cacheRevs, cacheFiles).init(repo, ancestorCset); |
78 ResolverImpl resolver = new ResolverImpl(); | 97 Transaction transaction = implRepo.getTransactionFactory().create(repo); |
79 for (Path f : m1.files()) { | 98 ResolverImpl resolver = new ResolverImpl(implRepo, dirstateBuilder, mergeStateBuilder); |
80 Nodeid fileRevBase, fileRevA, fileRevB; | 99 try { |
81 if (m2.contains(f)) { | 100 for (Path f : m1.files()) { |
82 fileRevA = m1.nodeid(f); | 101 Nodeid fileRevBase, fileRevA, fileRevB; |
83 fileRevB = m2.nodeid(f); | 102 if (m2.contains(f)) { |
84 fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; | 103 fileRevA = m1.nodeid(f); |
85 if (fileRevA.equals(fileRevB)) { | 104 fileRevB = m2.nodeid(f); |
86 HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 105 fileRevBase = ma.contains(f) ? ma.nodeid(f) : null; |
87 mediator.same(fr, fr, resolver); | 106 if (fileRevA.equals(fileRevB)) { |
88 } else if (fileRevBase == fileRevA) { | 107 HgFileRevision fr = new HgFileRevision(repo, fileRevA, m1.flags(f), f); |
89 assert fileRevBase != null; | 108 resolver.presentState(f, fr, fr); |
90 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 109 mediator.same(fr, resolver); |
91 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | 110 } else if (fileRevBase == fileRevA) { |
92 mediator.fastForwardB(frBase, frSecond, resolver); | 111 assert fileRevBase != null; |
93 } else if (fileRevBase == fileRevB) { | 112 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); |
94 assert fileRevBase != null; | 113 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); |
95 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 114 resolver.presentState(f, frBase, frSecond); |
96 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 115 mediator.fastForwardB(frBase, frSecond, resolver); |
97 mediator.fastForwardA(frBase, frFirst, resolver); | 116 } else if (fileRevBase == fileRevB) { |
117 assert fileRevBase != null; | |
118 HgFileRevision frBase = new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | |
119 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | |
120 resolver.presentState(f, frFirst, frBase); | |
121 mediator.fastForwardA(frBase, frFirst, resolver); | |
122 } else { | |
123 HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | |
124 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | |
125 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | |
126 resolver.presentState(f, frFirst, frSecond); | |
127 mediator.resolve(frBase, frFirst, frSecond, resolver); | |
128 } | |
98 } else { | 129 } else { |
99 HgFileRevision frBase = fileRevBase == null ? null : new HgFileRevision(repo, fileRevBase, ma.flags(f), f); | 130 // m2 doesn't contain the file, either new in m1, or deleted in m2 |
100 HgFileRevision frFirst = new HgFileRevision(repo, fileRevA, m1.flags(f), f); | 131 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); |
101 HgFileRevision frSecond= new HgFileRevision(repo, fileRevB, m2.flags(f), f); | 132 resolver.presentState(f, frFirst, null); |
102 mediator.resolve(frBase, frFirst, frSecond, resolver); | 133 if (ma.contains(f)) { |
134 // deleted in m2 | |
135 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | |
136 mediator.onlyA(frBase, frFirst, resolver); | |
137 } else { | |
138 // new in m1 | |
139 mediator.newInA(frFirst, resolver); | |
140 } | |
103 } | 141 } |
104 } else { | 142 resolver.apply(); |
105 // m2 doesn't contain the file, either new in m1, or deleted in m2 | 143 } // for m1 files |
106 HgFileRevision frFirst = new HgFileRevision(repo, m1.nodeid(f), m1.flags(f), f); | 144 for (Path f : m2.files()) { |
145 if (m1.contains(f)) { | |
146 continue; | |
147 } | |
148 HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); | |
149 // file in m2 is either new or deleted in m1 | |
150 resolver.presentState(f, null, frSecond); | |
107 if (ma.contains(f)) { | 151 if (ma.contains(f)) { |
108 // deleted in m2 | 152 // deleted in m1 |
109 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | 153 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); |
110 mediator.onlyA(frBase, frFirst, resolver); | 154 mediator.onlyB(frBase, frSecond, resolver); |
111 } else { | 155 } else { |
112 // new in m1 | 156 // new in m2 |
113 mediator.newInA(frFirst, resolver); | 157 mediator.newInB(frSecond, resolver); |
114 } | 158 } |
159 resolver.apply(); | |
115 } | 160 } |
116 resolver.apply(); | 161 resolver.serializeChanged(transaction); |
117 } // for m1 files | 162 transaction.commit(); |
118 for (Path f : m2.files()) { | 163 } catch (HgRuntimeException ex) { |
119 if (m1.contains(f)) { | 164 transaction.rollback(); |
120 continue; | 165 throw ex; |
121 } | 166 } catch (HgIOException ex) { |
122 HgFileRevision frSecond= new HgFileRevision(repo, m2.nodeid(f), m2.flags(f), f); | 167 transaction.rollback(); |
123 // file in m2 is either new or deleted in m1 | 168 throw ex; |
124 if (ma.contains(f)) { | |
125 // deleted in m1 | |
126 HgFileRevision frBase = new HgFileRevision(repo, ma.nodeid(f), ma.flags(f), f); | |
127 mediator.onlyB(frBase, frSecond, resolver); | |
128 } else { | |
129 // new in m2 | |
130 mediator.newInB(frSecond, resolver); | |
131 } | |
132 resolver.apply(); | |
133 } | 169 } |
134 } catch (HgRuntimeException ex) { | 170 } catch (HgRuntimeException ex) { |
135 throw new HgLibraryFailureException(ex); | 171 throw new HgLibraryFailureException(ex); |
136 } finally { | 172 } finally { |
137 wdLock.release(); | 173 wdLock.release(); |
158 secondCset = csetIndexB; | 194 secondCset = csetIndexB; |
159 ancestorCset = rmap.revisionIndex(ancestor); | 195 ancestorCset = rmap.revisionIndex(ancestor); |
160 } | 196 } |
161 | 197 |
162 /** | 198 /** |
163 * This is the way client code takes part in the merge process | 199 * This is the way client code takes part in the merge process. |
200 * It's advised to subclass {@link MediatorBase} unless special treatment for regular cases is desired | |
164 */ | 201 */ |
165 @Experimental(reason="Provisional API. Work in progress") | 202 @Experimental(reason="Provisional API. Work in progress") |
166 @Callback | 203 @Callback |
167 public interface Mediator { | 204 public interface Mediator { |
168 public void same(HgFileRevision first, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | 205 /** |
206 * file revisions are identical in both heads | |
207 */ | |
208 public void same(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | |
209 /** | |
210 * file left in first/left/A trunk only, deleted in second/right/B trunk | |
211 */ | |
169 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | 212 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; |
213 /** | |
214 * file left in second/right/B trunk only, deleted in first/left/A trunk | |
215 */ | |
170 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | 216 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; |
217 /** | |
218 * file is missing in ancestor revision and second/right/B trunk, introduced in first/left/A trunk | |
219 */ | |
171 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | 220 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; |
221 /** | |
222 * file is missing in ancestor revision and first/left/A trunk, introduced in second/right/B trunk | |
223 */ | |
172 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; | 224 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException; |
225 /** | |
226 * file was changed in first/left/A trunk, unchanged in second/right/B trunk | |
227 */ | |
173 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException; | 228 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException; |
229 /** | |
230 * file was changed in second/right/B trunk, unchanged in first/left/A trunk | |
231 */ | |
174 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | 232 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; |
233 /** | |
234 * File changed (or added, if base is <code>null</code>) in both trunks | |
235 */ | |
175 public void resolve(HgFileRevision base, HgFileRevision first, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; | 236 public void resolve(HgFileRevision base, HgFileRevision first, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException; |
176 } | 237 } |
177 | 238 |
178 /** | 239 /** |
179 * Clients shall not implement this interface. | 240 * Clients shall not implement this interface. |
180 * They use this API from inside {@link Mediator#resolve(HgFileRevision, HgFileRevision, HgFileRevision, Resolver)} | 241 * They use this API from inside {@link Mediator#resolve(HgFileRevision, HgFileRevision, HgFileRevision, Resolver)} |
181 */ | 242 */ |
182 @Experimental(reason="Provisional API. Work in progress") | 243 @Experimental(reason="Provisional API. Work in progress") |
183 public interface Resolver { | 244 public interface Resolver { |
184 public void use(HgFileRevision rev); | 245 public void use(HgFileRevision rev); |
185 public void use(InputStream content); | 246 /** |
247 * Replace current revision with stream content. | |
248 * Note, callers are not expected to {@link InputStream#close()} this stream. | |
249 * It will be {@link InputStream#close() closed} at <b>Hg4J</b>'s discretion | |
250 * not necessarily during invocation of this method. IOW, the library may decide to | |
251 * use this stream not right away, at some point of time later, and streams supplied | |
252 * shall respect this. | |
253 * | |
254 * @param content New content to replace current revision, shall not be <code>null</code> | |
255 * @throws IOException propagated exceptions from content | |
256 */ | |
257 public void use(InputStream content) throws IOException; | |
258 public void forget(HgFileRevision rev); | |
186 public void unresolved(); // record the file for later processing by 'hg resolve' | 259 public void unresolved(); // record the file for later processing by 'hg resolve' |
187 } | 260 } |
188 | 261 |
262 /** | |
263 * Base mediator implementation, with regular resolution | |
264 */ | |
265 @Experimental(reason="Provisional API. Work in progress") | |
266 public abstract class MediatorBase implements Mediator { | |
267 public void same(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | |
268 resolver.use(rev); | |
269 } | |
270 public void onlyA(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | |
271 resolver.use(rev); | |
272 } | |
273 public void onlyB(HgFileRevision base, HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | |
274 resolver.use(rev); | |
275 } | |
276 public void newInA(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | |
277 resolver.use(rev); | |
278 } | |
279 public void newInB(HgFileRevision rev, Resolver resolver) throws HgCallbackTargetException { | |
280 resolver.use(rev); | |
281 } | |
282 public void fastForwardA(HgFileRevision base, HgFileRevision first, Resolver resolver) throws HgCallbackTargetException { | |
283 resolver.use(first); | |
284 } | |
285 public void fastForwardB(HgFileRevision base, HgFileRevision second, Resolver resolver) throws HgCallbackTargetException { | |
286 resolver.use(second); | |
287 } | |
288 } | |
289 | |
189 private static class ResolverImpl implements Resolver { | 290 private static class ResolverImpl implements Resolver { |
190 void apply() { | 291 |
292 private final Internals repo; | |
293 private final DirstateBuilder dirstateBuilder; | |
294 private final MergeStateBuilder mergeStateBuilder; | |
295 private boolean changedDirstate; | |
296 private HgFileRevision revA; | |
297 private HgFileRevision revB; | |
298 private Path file; | |
299 // resolutions: | |
300 private HgFileRevision resolveUse, resolveForget; | |
301 private File resolveContent; | |
302 private boolean resolveMarkUnresolved; | |
303 | |
304 public ResolverImpl(Internals implRepo, DirstateBuilder dirstateBuilder, MergeStateBuilder mergeStateBuilder) { | |
305 repo = implRepo; | |
306 this.dirstateBuilder = dirstateBuilder; | |
307 this.mergeStateBuilder = mergeStateBuilder; | |
308 changedDirstate = false; | |
309 } | |
310 | |
311 void serializeChanged(Transaction tr) throws HgIOException { | |
312 if (changedDirstate) { | |
313 dirstateBuilder.serialize(tr); | |
314 } | |
315 mergeStateBuilder.serialize(tr); | |
316 } | |
317 | |
318 void presentState(Path p, HgFileRevision revA, HgFileRevision revB) { | |
319 assert revA != null || revB != null; | |
320 file = p; | |
321 this.revA = revA; | |
322 this.revB = revB; | |
323 resolveUse = resolveForget = null; | |
324 resolveContent = null; | |
325 resolveMarkUnresolved = false; | |
326 } | |
327 | |
328 void apply() throws HgIOException, HgRuntimeException { | |
329 if (resolveMarkUnresolved) { | |
330 mergeStateBuilder.unresolved(file); | |
331 } else if (resolveForget != null) { | |
332 if (resolveForget == revA) { | |
333 changedDirstate = true; | |
334 dirstateBuilder.recordRemoved(file); | |
335 } | |
336 } else if (resolveUse != null) { | |
337 if (resolveUse != revA) { | |
338 changedDirstate = true; | |
339 final WorkingDirFileWriter fw = new WorkingDirFileWriter(repo); | |
340 fw.processFile(resolveUse); | |
341 if (resolveUse == revB) { | |
342 dirstateBuilder.recordMergedFromP2(file); | |
343 } else { | |
344 dirstateBuilder.recordMerged(file, fw.fmode(), fw.mtime(), fw.bytesWritten()); | |
345 } | |
346 } // if resolution is to use revA, nothing to do | |
347 } else if (resolveContent != null) { | |
348 changedDirstate = true; | |
349 // FIXME write content to file using transaction? | |
350 InputStream is; | |
351 try { | |
352 is = new FileInputStream(resolveContent); | |
353 } catch (IOException ex) { | |
354 throw new HgIOException("Failed to read temporary content", ex, resolveContent); | |
355 } | |
356 final WorkingDirFileWriter fw = new WorkingDirFileWriter(repo); | |
357 fw.processFile(file, is, revA == null ? revB.getFileFlags() : revA.getFileFlags()); | |
358 // XXX if presentState(null, fileOnlyInB), and use(InputStream) - i.e. | |
359 // resolution is to add file with supplied content - shall I put 'Merged', MergedFromP2 or 'Added' into dirstate? | |
360 if (revA == null && revB != null) { | |
361 dirstateBuilder.recordMergedFromP2(file); | |
362 } else { | |
363 dirstateBuilder.recordMerged(file, fw.fmode(), fw.mtime(), fw.bytesWritten()); | |
364 } | |
365 } else { | |
366 assert false; | |
367 } | |
191 } | 368 } |
192 | 369 |
193 public void use(HgFileRevision rev) { | 370 public void use(HgFileRevision rev) { |
194 // TODO Auto-generated method stub | 371 if (rev == null) { |
195 } | 372 throw new IllegalArgumentException(); |
196 | 373 } |
197 public void use(InputStream content) { | 374 assert resolveContent == null; |
198 // TODO Auto-generated method stub | 375 assert resolveForget == null; |
376 resolveUse = rev; | |
377 } | |
378 | |
379 public void use(InputStream content) throws IOException { | |
380 if (content == null) { | |
381 throw new IllegalArgumentException(); | |
382 } | |
383 assert resolveUse == null; | |
384 assert resolveForget == null; | |
385 try { | |
386 // cache new contents just to fail fast if there are troubles with content | |
387 final FileUtils fileUtils = new FileUtils(repo.getLog(), this); | |
388 resolveContent = fileUtils.createTempFile(); | |
389 fileUtils.write(content, resolveContent); | |
390 } finally { | |
391 content.close(); | |
392 } | |
393 // do not care deleting file in case of failure to allow analyze of the issue | |
394 } | |
395 | |
396 public void forget(HgFileRevision rev) { | |
397 if (rev == null) { | |
398 throw new IllegalArgumentException(); | |
399 } | |
400 if (rev != revA || rev != revB) { | |
401 throw new IllegalArgumentException("Can't forget revision which doesn't represent actual state in either merged trunk"); | |
402 } | |
403 assert resolveUse == null; | |
404 assert resolveContent == null; | |
405 resolveForget = rev; | |
199 } | 406 } |
200 | 407 |
201 public void unresolved() { | 408 public void unresolved() { |
202 // TODO Auto-generated method stub | 409 resolveMarkUnresolved = true; |
203 } | 410 } |
204 } | 411 } |
205 } | 412 } |