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-2006 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.settings.convertors;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.beans.PropertyChangeSupport;
047: import java.io.BufferedOutputStream;
048: import java.io.ByteArrayOutputStream;
049: import java.io.IOException;
050: import java.io.OutputStream;
051: import java.io.OutputStreamWriter;
052: import java.io.Serializable;
053: import java.io.Writer;
054: import java.lang.ref.SoftReference;
055: import java.lang.ref.WeakReference;
056: import java.lang.reflect.Method;
057: import java.util.Arrays;
058: import java.util.Collections;
059: import java.util.logging.Level;
060: import javax.swing.JComponent;
061: import org.netbeans.modules.settings.Env;
062: import org.netbeans.modules.settings.ScheduledRequest;
063: import org.netbeans.spi.settings.Convertor;
064: import org.netbeans.spi.settings.Saver;
065: import org.openide.cookies.InstanceCookie;
066: import org.openide.cookies.SaveCookie;
067: import org.openide.filesystems.FileChangeAdapter;
068: import org.openide.filesystems.FileEvent;
069: import org.openide.filesystems.FileLock;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileSystem;
072: import org.openide.filesystems.FileUtil;
073: import org.openide.loaders.DataObject;
074: import org.openide.loaders.Environment;
075: import org.openide.loaders.InstanceDataObject;
076: import org.openide.modules.ModuleInfo;
077: import org.openide.nodes.Node;
078: import org.openide.util.Lookup;
079: import org.openide.util.SharedClassObject;
080: import org.openide.util.lookup.AbstractLookup;
081: import org.openide.util.lookup.InstanceContent;
082: import org.openide.windows.TopComponent;
083:
084: /** Convertor handles serialdata format described in
085: * http://www.netbeans.org/dtds/sessionsettings-1_0.dtd. The convertor replaces
086: * the old org.netbeans.core.projects.SerialDataConvertor.
087: * @author Jan Pokorsky
088: */
089: public final class SerialDataConvertor extends FileChangeAdapter
090: implements PropertyChangeListener, FileSystem.AtomicAction {
091: /** data object name cached in the attribute to prevent instance creation when
092: * its node is displayed.
093: * @see org.openide.loaders.InstanceDataObject#EA_NAME
094: */
095: static final String EA_NAME = "name"; // NOI18N
096: /** lock used to sync read/write operations for .settings file */
097: final Object READWRITE_LOCK = new Object();
098: private final InstanceContent lkpContent;
099: private final Lookup lookup;
100: private final DataObject dobj;
101: private final FileObject provider;
102: private final SerialDataConvertor.NodeConvertor node;
103: private SerialDataConvertor.SettingsInstance instance;
104: private SaveSupport saver;
105:
106: /** Creates a new instance of SDConvertor */
107: public SerialDataConvertor(DataObject dobj, FileObject provider) {
108: this .dobj = dobj;
109: this .provider = provider;
110: lkpContent = new InstanceContent();
111:
112: FileObject fo = dobj.getPrimaryFile();
113: fo.addFileChangeListener(FileUtil.weakFileChangeListener(this ,
114: fo));
115:
116: SerialDataConvertor.SettingsInstance si = createInstance(null);
117: if (isModuleEnabled(si)) {
118: instance = si;
119: lkpContent.add(instance);
120: }
121: lkpContent.add(this );
122: node = new SerialDataConvertor.NodeConvertor();
123: lkpContent.add(this , node);
124: lookup = new AbstractLookup(lkpContent);
125: }
126:
127: /** can store an object inst in the serialdata format
128: * @param w stream into which inst is written
129: * @param inst the setting object to be written
130: * @exception IOException if the object cannot be written
131: */
132: public void write(Writer w, Object inst) throws IOException {
133: XMLSettingsSupport.storeToXML10(inst, w, ModuleInfoManager
134: .getDefault().getModuleInfo(inst.getClass()));
135: }
136:
137: /** delegate to SaveSupport to handle an unfired setting object change
138: * @see SerialDataNode#resolvePropertyChange
139: */
140: void handleUnfiredChange() {
141: saver.propertyChange(null);
142: }
143:
144: DataObject getDataObject() {
145: return dobj;
146: }
147:
148: FileObject getProvider() {
149: return provider;
150: }
151:
152: /** provides content like InstanceCookie, SaveCokie */
153: public final Lookup getLookup() {
154: return lookup;
155: }
156:
157: /** create own InstanceCookie implementation */
158: private SettingsInstance createInstance(Object inst) {
159: return new SettingsInstance(inst);
160: }
161:
162: /** method provides a support storing the setting */
163: private SaveSupport createSaveSupport(Object inst) {
164: return new SaveSupport(inst);
165: }
166:
167: /** allow to listen on changes of the object inst; should be called when
168: * new instance is created */
169: private void attachToInstance(Object inst) {
170: SerialDataConvertor.SaveSupport _saver = null;
171: synchronized (this ) {
172: if (saver != null) {
173: saver.removePropertyChangeListener(this );
174: _saver = saver;
175: }
176: }
177:
178: if (_saver != null) {
179: /** creates new Thread and waits for finish - danger of deadlock,
180: * then called outside of lock*/
181: _saver.flush();
182: }
183:
184: synchronized (this ) {
185: saver = createSaveSupport(inst);
186: saver.addPropertyChangeListener(this );
187: }
188: }
189:
190: private void provideSaveCookie() {
191: if (saver.isChanged()) {
192: lkpContent.add(saver);
193: } else {
194: lkpContent.remove(saver);
195: }
196: }
197:
198: private void instanceCookieChanged(Object inst) {
199: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
200: XMLSettingsSupport.err.fine("instanceCookieChanged: "
201: + this .dobj); // NOI18N
202: if (saver != null) {
203: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
204: XMLSettingsSupport.err.fine("canceling saver: "
205: + this .dobj); // NOI18N
206: saver.removePropertyChangeListener(this );
207: getScheduledRequest().cancel();
208: saver = null;
209: }
210:
211: SerialDataConvertor.SettingsInstance si = createInstance(inst);
212:
213: //#34155 - is this already instantiated SystemOption?
214: boolean recreate = false;
215: if (instance != null && instance.getCachedInstance() != null) {
216: if (isSystemOption(instance.getCachedInstance())) {
217: recreate = true;
218: }
219: }
220: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
221: XMLSettingsSupport.err.fine("need recreate: " + recreate
222: + " for " + this .dobj); // NOI18N
223: if (isModuleEnabled(si)) {
224: instance = si;
225: lkpContent.set(Arrays.asList(new Object[] { this , si }),
226: null);
227: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
228: XMLSettingsSupport.err.fine("module enabled: "
229: + this .dobj); // NOI18N
230: } else {
231: lkpContent.set(Collections.singleton(this ), null);
232: instance = null;
233: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
234: XMLSettingsSupport.err.fine("module disabled: "
235: + this .dobj); // NOI18N
236: }
237:
238: lkpContent.add(this , node);
239:
240: //#34155 - if it was instantiated SystemOptions then force its recreation
241: // See issue for more details.
242: if (isModuleEnabled(si) && recreate) {
243: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
244: XMLSettingsSupport.err.fine("recreating: " + this .dobj); // NOI18N
245: try {
246: instance.instanceCreate();
247: } catch (Exception ex) {
248: XMLSettingsSupport.err.log(Level.WARNING, null, ex);
249: }
250: }
251: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
252: XMLSettingsSupport.err.fine("done: " + this .dobj); // NOI18N
253: }
254:
255: private static boolean isSystemOption(final Object obj) {
256: boolean b = false;
257: if (obj != null && obj instanceof SharedClassObject) {
258: for (Class c = obj.getClass(); !b && c != null; c = c
259: .getSuperclass()) {
260: b = "org.openide.options.SystemOption".equals(c
261: .getName());//NOI18N
262: }
263: }
264: return b;
265: }
266:
267: public void propertyChange(PropertyChangeEvent evt) {
268: if (evt == null)
269: return;
270:
271: String name = evt.getPropertyName();
272: if (name == null)
273: return;
274: // setting was changed
275: else if (name == SaveSupport.PROP_SAVE)
276: provideSaveCookie();
277: // .settings file was changed
278: else if (name == SaveSupport.PROP_FILE_CHANGED) {
279: miUnInitialized = true;
280: if (moduleCodeBase != null) {
281: ModuleInfo mi = ModuleInfoManager.getDefault()
282: .getModule(moduleCodeBase);
283: ModuleInfoManager.getDefault()
284: .unregisterPropertyChangeListener(this , mi);
285: }
286: instanceCookieChanged(null);
287: } else if (ModuleInfo.PROP_ENABLED
288: .equals(evt.getPropertyName())) {
289: instanceCookieChanged(null);
290: }
291: }
292:
293: /** process events coming from the file object*/
294: public void fileChanged(FileEvent fe) {
295: if (saver != null && fe.firedFrom(saver))
296: return;
297: propertyChange(new PropertyChangeEvent(this ,
298: SaveSupport.PROP_FILE_CHANGED, null, null));
299: }
300:
301: public void fileDeleted(FileEvent fe) {
302: if (saver != null && fe.firedFrom(saver))
303: return;
304: if (saver != null) {
305: saver.removePropertyChangeListener(this );
306: getScheduledRequest().cancel();
307: saver = null;
308: }
309: }
310:
311: private String moduleCodeBase = null;
312: private boolean miUnInitialized = true;
313: private boolean moduleMissing;
314:
315: private boolean isModuleEnabled(
316: SerialDataConvertor.SettingsInstance si) {
317: ModuleInfo mi = null;
318: if (miUnInitialized) {
319: moduleCodeBase = getModuleCodeNameBase(si);
320: miUnInitialized = false;
321: if (moduleCodeBase != null) {
322: mi = ModuleInfoManager.getDefault().getModule(
323: moduleCodeBase);
324: moduleMissing = (mi == null);
325: if (mi != null) {
326: ModuleInfoManager.getDefault()
327: .registerPropertyChangeListener(this , mi);
328: } else {
329: XMLSettingsSupport.err
330: .warning("Warning: unknown module code base: "
331: + // NOI18N
332: moduleCodeBase + " in " + // NOI18N
333: getDataObject().getPrimaryFile());
334: }
335: } else {
336: moduleMissing = false;
337: }
338: } else {
339: mi = ModuleInfoManager.getDefault().getModule(
340: moduleCodeBase);
341: }
342:
343: return !moduleMissing && (mi == null || mi.isEnabled());
344: }
345:
346: private String getModuleCodeNameBase(
347: SerialDataConvertor.SettingsInstance si) {
348: try {
349: String module = si.getSettings(true).getCodeNameBase();
350: return module;
351: } catch (IOException ex) {
352: XMLSettingsSupport.err.log(Level.WARNING, null, ex);
353: }
354: return null;
355: }
356:
357: /** Little utility method for posting an exception
358: * to the default <CODE>ErrorManager</CODE> with severity
359: * <CODE>ErrorManager.INFORMATIONAL</CODE>
360: */
361: static void inform(Throwable t) {
362: XMLSettingsSupport.err.log(Level.WARNING, null, t);
363: }
364:
365: /** called by ScheduledRequest in order to perform the request */
366: public void run() throws IOException {
367: saver.writeDown();
368: }
369:
370: /** scheduled request to store setting */
371: private ScheduledRequest request;
372:
373: /** get the scheduled request to store setting */
374: private synchronized ScheduledRequest getScheduledRequest() {
375: if (request == null) {
376: request = new ScheduledRequest(this .getDataObject()
377: .getPrimaryFile(), this );
378: }
379: return request;
380: }
381:
382: //////////////////////////////////////////////////////////////////////////
383: // SettingsInstance
384: //////////////////////////////////////////////////////////////////////////
385:
386: /** InstanceCookie implementation */
387: private final class SettingsInstance implements InstanceCookie.Of {
388:
389: /** created instance */
390: private SoftReference<Object> inst;
391:
392: /* Lifecycle of SettingsRecognizer:
393: * Initially: settings = null
394: * Parsed header: settings created, light object (no byte[], no char[])
395: * Full parsing: Create char[], convert it to byte[] and release char[]
396: * create instance, throw away settings
397: *
398: */
399: /** holder of parsed settings */
400: private XMLSettingsSupport.SettingsRecognizer settings = null;
401:
402: /** Creates new SettingsInstance */
403: public SettingsInstance(Object instance) {
404: setCachedInstance(instance);
405: }
406:
407: /** Getter for parsed settings
408: * @param header if <code>true</code> parse just header(instanceof, module, classname)
409: */
410: private XMLSettingsSupport.SettingsRecognizer getSettings(
411: boolean header) throws IOException {
412: synchronized (this ) {
413: if (settings == null) {
414: synchronized (READWRITE_LOCK) {
415: settings = new XMLSettingsSupport.SettingsRecognizer(
416: header, getDataObject()
417: .getPrimaryFile());
418: settings.parse();
419: }
420: return settings;
421: }
422: if (!header) {
423: if (!settings.isAllRead()) {
424: settings.setAllRead(false);
425: settings.parse();
426: }
427: }
428:
429: return settings;
430: }
431: }
432:
433: public Object instanceCreate() throws IOException,
434: ClassNotFoundException {
435: Object inst;
436: XMLSettingsSupport.SettingsRecognizer recog;
437:
438: synchronized (this ) {
439: inst = getCachedInstance();
440: if (inst != null) {
441: if (XMLSettingsSupport.err.isLoggable(Level.FINE))
442: XMLSettingsSupport.err
443: .fine("Cached instance1: " + inst); // NOI18N
444: return inst;
445: }
446: }
447:
448: recog = getSettings(false);
449: inst = recog.instanceCreate();
450:
451: synchronized (this ) {
452: Object existing = getCachedInstance();
453: if (existing != null) {
454: if (XMLSettingsSupport.err.isLoggable(Level.FINER))
455: XMLSettingsSupport.err
456: .finer("Cached instance2: " + existing); // NOI18N
457: return existing;
458: }
459: setCachedInstance(inst);
460: }
461: if (XMLSettingsSupport.err.isLoggable(Level.FINER))
462: XMLSettingsSupport.err.finer("Attached to instance: "
463: + inst); // NOI18N
464: attachToInstance(inst);
465:
466: return inst;
467: }
468:
469: public Class instanceClass() throws IOException,
470: ClassNotFoundException {
471: // cached
472: Object inst = getCachedInstance();
473: if (inst != null) {
474: return inst.getClass();
475: }
476:
477: XMLSettingsSupport.SettingsRecognizer recog = getSettings(false);
478: return recog.instanceClass();
479: }
480:
481: public boolean instanceOf(Class<?> type) {
482: try {
483: if (moduleCodeBase != null
484: && ModuleInfoManager.getDefault().isReloaded(
485: moduleCodeBase)
486: && type.getClassLoader() != ClassLoader
487: .getSystemClassLoader()
488: && type.getClassLoader() != null) {
489: // special treatment for classes that could be reloaded
490: ModuleInfo info = ModuleInfoManager.getDefault()
491: .getModule(moduleCodeBase);
492: if (info == null || !info.isEnabled()) {
493: // false to disabled modules
494: return false;
495: }
496: // otherwise really try to load
497: Class<?> instanceType = instanceClass();
498: return type.isAssignableFrom(instanceType);
499: }
500:
501: // check existing instance first:
502: Object inst = getCachedInstance();
503: if (inst != null) {
504: return type.isInstance(inst);
505: }
506:
507: // check the settings cache/file
508: return getSettings(true).getInstanceOf().contains(
509: type.getName());
510: } catch (ClassNotFoundException ex) {
511: XMLSettingsSupport.err.log(Level.WARNING,
512: getDataObject().getPrimaryFile().toString());
513: inform(ex);
514: } catch (IOException ex) {
515: XMLSettingsSupport.err.log(Level.WARNING,
516: getDataObject().getPrimaryFile().toString());
517: inform(ex);
518: }
519: return false;
520: }
521:
522: public String instanceName() {
523: // try cached instance
524: Object inst = getCachedInstance();
525: if (inst != null) {
526: return inst.getClass().getName();
527: }
528:
529: try {
530: return getSettings(true).instanceName();
531: } catch (IOException ex) {
532: XMLSettingsSupport.err.warning(getDataObject()
533: .getPrimaryFile().toString());
534: inform(ex);
535: return ""; // NOI18N
536: }
537: }
538:
539: private Object getCachedInstance() {
540: return inst.get();
541: }
542:
543: private void setCachedInstance(Object o) {
544: inst = new SoftReference<Object>(o);
545: settings = null; // clear reference to settings
546: }
547:
548: // called by InstanceDataObject to set new object
549: public void setInstance(Object inst, boolean save)
550: throws IOException {
551: instanceCookieChanged(inst);
552: if (inst != null) {
553: attachToInstance(inst);
554: if (save)
555: getScheduledRequest().runAndWait();
556: }
557: }
558:
559: }
560:
561: /** Support handles automatic setting objects storing and allows to identify
562: * the origin of file events fired as a consequence of this storing
563: */
564: private final class SaveSupport implements FileSystem.AtomicAction,
565: SaveCookie, PropertyChangeListener, Saver {
566: /** property means setting is changed and should be changed */
567: public static final String PROP_SAVE = "savecookie"; //NOI18N
568: /** property means setting file content is changed */
569: public static final String PROP_FILE_CHANGED = "fileChanged"; //NOI18N
570:
571: /** support for PropertyChangeListeners */
572: private PropertyChangeSupport changeSupport;
573: /** the number of registered PropertyChangeListeners */
574: private int propertyChangeListenerCount = 0;
575:
576: /** setting is already changed */
577: private boolean isChanged = false;
578: /** file containing persisted setting */
579: private final FileObject file;
580: /** weak reference to setting object */
581: private final WeakReference<Object> instance;
582: /** remember whether the DataObject is a template or not; calling isTemplate() is slow */
583: private Boolean knownToBeTemplate = null;
584: /** the setting object is serialized, if true ignore prop. change
585: * notifications
586: */
587: private boolean isWriting = false;
588: /** convertor for possible format upgrade */
589: private Convertor convertor;
590:
591: /** Creates a new instance of SaveSupport */
592: public SaveSupport(Object inst) {
593: this .instance = new WeakReference<Object>(inst);
594: file = getDataObject().getPrimaryFile();
595: }
596:
597: /** is setting object changed? */
598: public final boolean isChanged() {
599: return isChanged;
600: }
601:
602: /** store setting or provide just SaveCookie? */
603: private boolean acceptSave() {
604: Object inst = instance.get();
605: if (inst == null || !(inst instanceof Serializable) ||
606: // XXX bad dep; should perhaps have some marker in the .settings file for this??
607: inst instanceof TopComponent)
608: return false;
609:
610: return true;
611: }
612:
613: /** place where to filter events comming from setting object */
614: private boolean ignoreChange(PropertyChangeEvent pce) {
615: if (isChanged || isWriting || !getDataObject().isValid())
616: return true;
617:
618: // undocumented workaround used in 3.3; since 3.4 convertors make
619: // possible to customize the setting change notification filtering
620: if (pce != null
621: && Boolean.FALSE.equals(pce.getPropagationId()))
622: return true;
623:
624: if (knownToBeTemplate == null)
625: knownToBeTemplate = getDataObject().isTemplate() ? Boolean.TRUE
626: : Boolean.FALSE;
627: return knownToBeTemplate.booleanValue();
628: }
629:
630: /** get convertor for possible upgrade; can be null */
631: private Convertor getConvertor() {
632: return convertor;
633: }
634:
635: /** try to find out convertor for possible upgrade and cache it; can be null */
636: private Convertor initConvertor() {
637: Object inst = instance.get();
638: if (inst == null) {
639: throw new IllegalStateException(
640: "setting object cannot be null: "
641: + getDataObject());// NOI18N
642: }
643:
644: try {
645: FileObject newProviderFO = Env.findProvider(inst
646: .getClass());
647: if (newProviderFO != null) {
648: FileObject foEntity = Env
649: .findEntityRegistration(newProviderFO);
650: if (foEntity == null)
651: foEntity = newProviderFO;
652: Object attrb = foEntity
653: .getAttribute(Env.EA_PUBLICID);
654: if (attrb == null || !(attrb instanceof String)) {
655: throw new IOException(
656: "missing or invalid attribute: "
657: + //NOI18N
658: Env.EA_PUBLICID
659: + ", provider: " + foEntity); //NOI18N
660: }
661: if (XMLSettingsSupport.INSTANCE_DTD_ID
662: .equals(attrb)) {
663: convertor = null;
664: return convertor;
665: }
666:
667: attrb = newProviderFO
668: .getAttribute(Env.EA_CONVERTOR);
669: if (attrb == null || !(attrb instanceof Convertor)) {
670: throw new IOException(
671: "cannot create convertor: "
672: + //NOI18N
673: attrb + ", provider: "
674: + newProviderFO); //NOI18N
675: } else {
676: convertor = (Convertor) attrb;
677: return convertor;
678: }
679: }
680: } catch (IOException ex) {
681: inform(ex);
682: }
683: return convertor;
684: }
685:
686: /** Registers PropertyChangeListener to receive events and initialize
687: * listening to events comming from the setting object and file object.
688: * @param listener The listener to register.
689: */
690: public synchronized void addPropertyChangeListener(
691: PropertyChangeListener listener) {
692: if (changeSupport == null
693: || propertyChangeListenerCount <= 0) {
694: Object inst = instance.get();
695: if (inst == null)
696: return;
697: if (changeSupport == null) {
698: changeSupport = new PropertyChangeSupport(this );
699: propertyChangeListenerCount = 0;
700: }
701: Convertor conv = initConvertor();
702: if (conv != null) {
703: conv.registerSaver(inst, this );
704: } else {
705: registerPropertyChangeListener(inst);
706: }
707: }
708: propertyChangeListenerCount++;
709: changeSupport.addPropertyChangeListener(listener);
710: }
711:
712: /** Removes PropertyChangeListener from the list of listeners.
713: * @param listener The listener to remove.
714: */
715: public synchronized void removePropertyChangeListener(
716: PropertyChangeListener listener) {
717: if (changeSupport == null)
718: return;
719:
720: propertyChangeListenerCount--;
721: changeSupport.removePropertyChangeListener(listener);
722:
723: if (propertyChangeListenerCount == 0) {
724: Object inst = instance.get();
725: if (inst == null)
726: return;
727:
728: Convertor conv = getConvertor();
729: if (conv != null) {
730: conv.unregisterSaver(inst, this );
731: } else {
732: unregisterPropertyChangeListener(inst);
733: }
734: }
735: }
736:
737: /** try to register PropertyChangeListener to the setting object
738: * to be notified about its changes.
739: */
740: private void registerPropertyChangeListener(Object inst) {
741: if (inst instanceof SharedClassObject) {
742: ((SharedClassObject) inst)
743: .addPropertyChangeListener(this );
744: } else if (inst instanceof JComponent) {
745: ((JComponent) inst).addPropertyChangeListener(this );
746: } else {
747: // add propertyChangeListener
748: try {
749: Method method = inst
750: .getClass()
751: .getMethod(
752: "addPropertyChangeListener", // NOI18N
753: new Class[] { PropertyChangeListener.class });
754: method.invoke(inst, new Object[] { this });
755: } catch (NoSuchMethodException ex) {
756: // just changes done through gui will be saved
757: if (XMLSettingsSupport.err.isLoggable(Level.FINE)) {
758: XMLSettingsSupport.err
759: .warning("NoSuchMethodException: "
760: + // NOI18N
761: inst.getClass().getName()
762: + ".addPropertyChangeListener"); // NOI18N
763: }
764: } catch (IllegalAccessException ex) {
765: // just changes done through gui will be saved
766: XMLSettingsSupport.err.warning("Instance: " + inst); // NOI18N
767: XMLSettingsSupport.err.log(Level.WARNING, null, ex);
768: } catch (java.lang.reflect.InvocationTargetException ex) {
769: // just changes done through gui will be saved
770: XMLSettingsSupport.err.log(Level.WARNING, null, ex
771: .getTargetException());
772: }
773: }
774: }
775:
776: /** @see #registerPropertyChangeListener
777: */
778: private void unregisterPropertyChangeListener(Object inst) {
779: try {
780: Method method = inst.getClass().getMethod(
781: "removePropertyChangeListener", // NOI18N
782: new Class[] { PropertyChangeListener.class });
783: method.invoke(inst, new Object[] { this });
784: } catch (NoSuchMethodException ex) {
785: // just changes done through gui will be saved
786: if (XMLSettingsSupport.err.isLoggable(Level.FINE)) {
787: XMLSettingsSupport.err.log(Level.FINE,
788: "NoSuchMethodException: "
789: + // NOI18N
790: inst.getClass().getName()
791: + ".removePropertyChangeListener"); // NOI18N
792: }
793: } catch (IllegalAccessException ex) {
794: XMLSettingsSupport.err.log(Level.WARNING, "Instance: "
795: + inst); // NOI18N
796: XMLSettingsSupport.err.log(Level.WARNING, null, ex);
797: } catch (java.lang.reflect.InvocationTargetException ex) {
798: XMLSettingsSupport.err.log(Level.WARNING, null, ex
799: .getTargetException());
800: }
801: }
802:
803: /** Notifies all registered listeners about the event.
804: * @param event The event to be fired
805: * @see #PROP_FILE_CHANGED
806: * @see #PROP_SAVE
807: */
808: private void firePropertyChange(String name) {
809: if (changeSupport != null)
810: changeSupport.firePropertyChange(name, null, null);
811: }
812:
813: /** force to finish scheduled request */
814: public void flush() {
815: getScheduledRequest().forceToFinish();
816: }
817:
818: private ByteArrayOutputStream buf;
819:
820: /** process events coming from a setting object */
821: public final void propertyChange(PropertyChangeEvent pce) {
822: if (ignoreChange(pce))
823: return;
824: isChanged = true;
825: firePropertyChange(PROP_SAVE);
826: if (acceptSave()) {
827: getScheduledRequest().schedule(instance.get());
828: }
829: }
830:
831: public void markDirty() {
832: if (ignoreChange(null))
833: return;
834: isChanged = true;
835: firePropertyChange(PROP_SAVE);
836: }
837:
838: public void requestSave() throws IOException {
839: if (ignoreChange(null))
840: return;
841: isChanged = true;
842: firePropertyChange(PROP_SAVE);
843: getScheduledRequest().schedule(instance.get());
844: }
845:
846: /** store buffer to the file. */
847: public void run() throws IOException {
848: if (!getDataObject().isValid()) {
849: //invalid data object cannot be used for storing
850: if (XMLSettingsSupport.err.isLoggable(Level.FINE)) {
851: XMLSettingsSupport.err
852: .fine("invalid data object cannot be used for storing "
853: + getDataObject()); // NOI18N
854: }
855: return;
856: }
857:
858: try {
859: try2run();
860: } catch (IOException ex) {
861: //#25288: DO can be invalidated asynchronously (module disabling)
862: //then ignore IO exceptions
863: if (getDataObject().isValid()) {
864: throw ex;
865: } else {
866: return;
867: }
868: }
869: }
870:
871: /** try to perform atomic action */
872: private void try2run() throws IOException {
873: FileLock lock;
874: OutputStream los;
875: synchronized (READWRITE_LOCK) {
876: if (XMLSettingsSupport.err.isLoggable(Level.FINER)) {
877: XMLSettingsSupport.err.finer("saving "
878: + getDataObject()); // NOI18N
879: }
880: lock = getScheduledRequest().getFileLock();
881: if (lock == null)
882: return;
883: los = file.getOutputStream(lock);
884:
885: OutputStream os = new BufferedOutputStream(los, 1024);
886: try {
887: buf.writeTo(os);
888: if (XMLSettingsSupport.err.isLoggable(Level.FINER)) {
889: XMLSettingsSupport.err.finer("saved " + dobj); // NOI18N
890: }
891: } finally {
892: os.close();
893: }
894: }
895: }
896:
897: /** Implementation of SaveCookie. */
898: public void save() throws IOException {
899: if (!isChanged)
900: return;
901: getScheduledRequest().runAndWait();
902: }
903:
904: /** store the setting object even if was not changed */
905: private void writeDown() throws IOException {
906: Object inst = instance.get();
907: if (inst == null)
908: return;
909:
910: ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
911: Writer w = new OutputStreamWriter(b, "UTF-8"); // NOI18N
912: try {
913: isWriting = true;
914: Convertor conv = getConvertor();
915: if (conv != null) {
916: conv.write(w, inst);
917: } else {
918: write(w, inst);
919: }
920: } finally {
921: w.close();
922: isWriting = false;
923: }
924: isChanged = false;
925:
926: buf = b;
927: file.getFileSystem().runAtomicAction(this );
928: buf = null;
929: if (!isChanged)
930: firePropertyChange(PROP_SAVE);
931: }
932: }
933:
934: ////////////////////////////////////////////////////////////////////////////
935: // Provider
936: ////////////////////////////////////////////////////////////////////////////
937:
938: /** A provider for .settings files containing serial data format
939: * (hexa stream)
940: */
941: public final static class Provider implements Environment.Provider {
942: private final FileObject providerFO;
943:
944: public static Environment.Provider create(FileObject fo) {
945: return new Provider(fo);
946: }
947:
948: private Provider(FileObject fo) {
949: providerFO = fo;
950: }
951:
952: public Lookup getEnvironment(DataObject dobj) {
953: if (!(dobj instanceof InstanceDataObject))
954: return Lookup.EMPTY;
955: return new SerialDataConvertor(dobj, providerFO)
956: .getLookup();
957: }
958:
959: }
960:
961: ////////////////////////////////////////////////////////////////////////////
962: // NodeConvertor
963: ////////////////////////////////////////////////////////////////////////////
964:
965: /** allow to postpone the node creation */
966: private static final class NodeConvertor implements
967: InstanceContent.Convertor<SerialDataConvertor, Node> {
968: NodeConvertor() {
969: }
970:
971: public Node convert(SerialDataConvertor o) {
972: return new SerialDataNode(o);
973: }
974:
975: public Class<Node> type(SerialDataConvertor o) {
976: return Node.class;
977: }
978:
979: public String id(SerialDataConvertor o) {
980: // Generally irrelevant in this context.
981: return o.toString();
982: }
983:
984: public String displayName(SerialDataConvertor o) {
985: // Again, irrelevant here.
986: return o.toString();
987: }
988:
989: }
990:
991: }
|