001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.cnd.modelimpl.csm.core;
043:
044: import java.util.*;
045: import org.netbeans.modules.cnd.api.model.CsmFile;
046: import org.netbeans.modules.cnd.api.model.CsmModelAccessor;
047: import org.netbeans.modules.cnd.api.model.CsmNamespace;
048: import org.netbeans.modules.cnd.api.model.CsmOffsetableDeclaration;
049: import org.netbeans.modules.cnd.api.model.CsmProject;
050: import org.netbeans.modules.cnd.modelimpl.debug.Diagnostic;
051: import org.netbeans.modules.cnd.modelimpl.debug.TraceFlags;
052:
053: /**
054: * Coordiates all change notifications.
055: * Singleton.
056: * @author vk155633
057: */
058: public class Notificator {
059:
060: private static ThreadLocal<Notificator> instance = new ThreadLocal<Notificator>() {
061: protected Notificator initialValue() {
062: return new Notificator();
063: }
064: };
065:
066: private int transactionLevel = 0;
067: private ChangeEventImpl currEvent;
068: private ModelImpl model;
069:
070: private Notificator() {
071: if (CsmModelAccessor.getModel() instanceof ModelImpl) {
072: model = (ModelImpl) CsmModelAccessor.getModel();
073: }
074: }
075:
076: public static Notificator instance() {
077: return instance.get();
078: }
079:
080: private String getCurrThreadString() {
081: Thread thread = Thread.currentThread();
082: return thread.getName() + ' ' + thread.hashCode();
083: }
084:
085: public void startTransaction() {
086: synchronized (this ) {
087: transactionLevel++;
088: if (TraceFlags.DEBUG)
089: Diagnostic.trace(" > " + transactionLevel + ' '
090: + getCurrThreadString()); // NOI18N
091: resetEvent();
092: }
093: }
094:
095: public void endTransaction() {
096: synchronized (this ) {
097: transactionLevel--;
098: if (TraceFlags.DEBUG)
099: Diagnostic.trace(" < " + transactionLevel + ' '
100: + getCurrThreadString()); // NOI18N
101: if (transactionLevel <= 0) {
102: flush();
103: }
104: }
105: }
106:
107: private ChangeEventImpl getEvent() {
108: if (currEvent == null) {
109: //synchronized( this ) {
110: //if( currEvent == null ) {
111: // TODO: think over, whether this does not contain a well-known double-check problem
112: ChangeEventImpl ev = new ChangeEventImpl(this );
113: currEvent = ev;
114: //}
115: //}
116: }
117: return currEvent;
118: }
119:
120: private void resetEvent() {
121: currEvent = null;
122: }
123:
124: // FIXUP: there should be a notificator per project instead!
125: public void reset() {
126: synchronized (this ) {
127: resetEvent();
128: }
129: }
130:
131: private boolean isEventEmpty() {
132: return currEvent == null || currEvent.isEmpty();
133: }
134:
135: public void registerNewFile(CsmFile file) {
136: synchronized (this ) {
137: getEvent().addNewFile(file);
138: }
139: }
140:
141: public void registerRemovedFile(CsmFile file) {
142: synchronized (this ) {
143: getEvent().addRemovedFile(file);
144: }
145: }
146:
147: public void registerChangedFile(CsmFile file) {
148: synchronized (this ) {
149: getEvent().addChangedFile(file);
150: }
151: }
152:
153: public void registerNewDeclaration(CsmOffsetableDeclaration decl) {
154: synchronized (this ) {
155: getEvent().addNewDeclaration(decl);
156: }
157: }
158:
159: public void registerRemovedDeclaration(CsmOffsetableDeclaration decl) {
160: synchronized (this ) {
161: getEvent().addRemovedDeclaration(decl);
162: }
163: }
164:
165: public void registerChangedDeclaration(
166: CsmOffsetableDeclaration oldDecl,
167: CsmOffsetableDeclaration newDecl) {
168: synchronized (this ) {
169: getEvent().addChangedDeclaration(oldDecl, newDecl);
170: }
171: }
172:
173: public void registerNewNamespace(CsmNamespace ns) {
174: synchronized (this ) {
175: getEvent().addNewNamespace(ns);
176: }
177: }
178:
179: public void registerRemovedNamespace(CsmNamespace ns) {
180: synchronized (this ) {
181: getEvent().addRemovedNamespace(ns);
182: }
183: }
184:
185: /**
186: * Generally, we should rely on hashCode() and equals()
187: * of the CsmFile && CsmDeclaration.
188: *
189: * But for now (last day of Deimos project) it's much easier
190: * to ensure this here than to write/test/debug hashCode() and equals()
191: *
192: * TODO: ensure correct hashCode() and equals()
193: * in CsmFile && CsmDeclaration, remove IdMaker and related
194: */
195: private interface IdMaker {
196: Object id(Object o);
197: }
198:
199: public void flush() {
200:
201: ChangeEventImpl ev;
202:
203: synchronized (this ) {
204: transactionLevel = 0;
205: if (isEventEmpty()) {
206: return;
207: }
208: ev = getEvent();
209: resetEvent();
210: }
211:
212: IdMaker idMaker;
213:
214: idMaker = new IdMaker() {
215: public Object id(Object o) {
216: return ((CsmFile) o).getAbsolutePath();
217: }
218: };
219: processFiles(idMaker, ev.getNewFiles(), ev.getRemovedFiles(),
220: ev.getChangedFiles());
221:
222: idMaker = new IdMaker() {
223: public Object id(Object o) {
224: return PersistentKey
225: .createKey((CsmOffsetableDeclaration) o);
226: }
227: };
228: processDeclarations(idMaker, ev.getNewDeclarations(), ev
229: .getRemovedDeclarations(), ev.getChangedDeclarations());
230:
231: gatherProjects(ev);
232:
233: //TODO: thik over, probably it's worth keeping this "!="
234: //if( model != null ) {
235: ListenersImpl.getImpl().fireModelChanged(ev);
236: //}
237: }
238:
239: private static void gatherProjects(ChangeEventImpl ev) {
240: Collection<CsmProject> projects = ev.getChangedProjects();
241: Collection/*CsmFile*/[] files = new Collection/*CsmFile*/[] {
242: ev.getNewFiles(), ev.getChangedFiles(),
243: ev.getRemovedFiles() };
244: for (int i = 0; i < files.length; i++) {
245: for (Iterator iter = files[i].iterator(); iter.hasNext();) {
246: projects.add(((CsmFile) iter.next()).getProject());
247: }
248: }
249: Collection/*CsmOffsetableDeclaration*/[] decls = new Collection/*CsmOffsetableDeclaration*/[] {
250: ev.getNewDeclarations(),
251: ev.getChangedDeclarations().values(),
252: ev.getRemovedDeclarations() };
253: for (int i = 0; i < decls.length; i++) {
254: for (Iterator iter = decls[i].iterator(); iter.hasNext();) {
255: Object o = iter.next();
256: if (o instanceof CsmOffsetableDeclaration) {
257: projects.add(((CsmOffsetableDeclaration) o)
258: .getContainingFile().getProject());
259: }
260: }
261: }
262: }
263:
264: private static void processFiles(IdMaker idMaker, Collection added,
265: Collection removed, Collection changed) {
266:
267: Set idsAdded = new HashSet();
268: for (Iterator iter = added.iterator(); iter.hasNext();) {
269: idsAdded.add(idMaker.id(iter.next()));
270: }
271:
272: Set idsRemoved = new HashSet();
273: for (Iterator iter = removed.iterator(); iter.hasNext();) {
274: idsRemoved.add(idMaker.id(iter.next()));
275: }
276:
277: Set rightAdded = new HashSet();
278: Set rightRemoved = new HashSet();
279:
280: for (Iterator iter = removed.iterator(); iter.hasNext();) {
281: Object o = iter.next();
282: Object id = idMaker.id(o);
283: if (idsAdded.contains(id)) {
284: changed.add(o);
285: } else {
286: rightRemoved.add(o);
287: }
288: }
289:
290: for (Iterator iter = added.iterator(); iter.hasNext();) {
291: Object o = iter.next();
292: Object id = idMaker.id(o);
293: if (!idsRemoved.contains(id)) {
294: rightAdded.add(o);
295: }
296: }
297:
298: added.clear();
299: added.addAll(rightAdded);
300:
301: removed.clear();
302: removed.addAll(rightRemoved);
303: }
304:
305: private static void processDeclarations(
306: IdMaker idMaker,
307: Collection<CsmOffsetableDeclaration> added,
308: Collection<CsmOffsetableDeclaration> removed,
309: Map<CsmOffsetableDeclaration, CsmOffsetableDeclaration> changed) {
310:
311: Map<Object, CsmOffsetableDeclaration> idsAdded = new HashMap<Object, CsmOffsetableDeclaration>();
312: for (CsmOffsetableDeclaration decl : added) {
313: idsAdded.put(idMaker.id(decl), decl);
314: }
315:
316: Map<Object, CsmOffsetableDeclaration> idsRemoved = new HashMap<Object, CsmOffsetableDeclaration>();
317: for (CsmOffsetableDeclaration decl : removed) {
318: idsRemoved.put(idMaker.id(decl), decl);
319: }
320:
321: Set<CsmOffsetableDeclaration> rightAdded = new HashSet<CsmOffsetableDeclaration>();
322: Set<CsmOffsetableDeclaration> rightRemoved = new HashSet<CsmOffsetableDeclaration>();
323:
324: for (CsmOffsetableDeclaration decl : removed) {
325: Object id = idMaker.id(decl);
326: if (idsAdded.containsKey(id)) {
327: changed.put(decl, idsAdded.get(id));
328: } else {
329: rightRemoved.add(decl);
330: }
331: }
332:
333: for (CsmOffsetableDeclaration decl : added) {
334: Object id = idMaker.id(decl);
335: if (!idsRemoved.containsKey(id)) {
336: rightAdded.add(decl);
337: }
338: }
339:
340: added.clear();
341: added.addAll(rightAdded);
342:
343: removed.clear();
344: //removed.addAll(rightRemoved);
345: if (rightRemoved.size() > 0) {
346: for (CsmOffsetableDeclaration decl : rightRemoved) {
347: CharSequence uniqueName = decl.getUniqueName();
348: CsmProject project = decl.getContainingFile()
349: .getProject();
350: CsmOffsetableDeclaration duplicated = (CsmOffsetableDeclaration) project
351: .findDeclaration(uniqueName);
352: if (duplicated != null) {
353: changed.put(decl, duplicated);
354: } else {
355: removed.add(decl);
356: }
357: }
358: }
359: }
360: }
|