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.openide.loaders;
043:
044: import java.io.*;
045: import java.util.*;
046: import java.util.logging.*;
047: import javax.swing.Action;
048: import org.openide.filesystems.*;
049: import org.openide.nodes.NodeOp;
050: import org.openide.util.*;
051: import org.openide.util.actions.SystemAction;
052: import org.openide.util.io.SafeException;
053:
054: /** A data loader recognizes {@link FileObject}s and creates appropriate
055: * {@link DataObject}s to represent them.
056: * The created data object must be a subclass
057: * of the <EM>representation class</EM> provided in the constructor.
058: * <P>
059: * Subclasses of <code>DataLoader</code> should be made <EM>JavaBeans</EM> with
060: * additional parameters, so a user may configure the loaders in the loader pool.
061: *
062: * @author Jaroslav Tulach
063: */
064: public abstract class DataLoader extends SharedClassObject {
065: /** error manager for logging the happenings in loaders */
066: static final Logger ERR = Logger
067: .getLogger("org.openide.loaders.DataLoader"); // NOI18N
068:
069: // XXX why is this necessary? otherwise reading loader pool now throws heavy
070: // InvalidClassException's reading (abstract!) DataLoader...? --jglick
071: private static final long serialVersionUID = 1986614061378346169L;
072:
073: /** property name of display name */
074: public static final String PROP_DISPLAY_NAME = "displayName"; // NOI18N
075: /** property name of list of actions */
076: public static final String PROP_ACTIONS = "actions"; // NOI18N
077: /** property name of list of default actions */
078: private static final String PROP_DEF_ACTIONS = "defaultActions"; // NOI18N
079: /** key to hold reference to out action manager */
080: private static final Object ACTION_MANAGER = new Object();
081: /** representation class, not public property */
082: private static final Object PROP_REPRESENTATION_CLASS = new Object();
083: /** representation class name, not public property */
084: private static final Object PROP_REPRESENTATION_CLASS_NAME = new Object();
085:
086: private static final int LOADER_VERSION = 1;
087:
088: /** Create a new data loader.
089: * Pass its representation class as a parameter to the constructor.
090: * It is recommended that representation class is superclass of all
091: * DataObjects produced by the loaded, but it is not required any more.
092: *
093: * @param representationClass the superclass (not necessarily) of all objects
094: * returned from {@link #findDataObject}. The class may be anything but
095: * should be chosen to be as close as possible to the actual class of objects returned from the loader,
096: * to best identify the loader's data objects to listeners.
097: * @deprecated Use {@link #DataLoader(String)} instead.
098: */
099: @Deprecated
100: protected DataLoader(Class<? extends DataObject> representationClass) {
101: putProperty(PROP_REPRESENTATION_CLASS, representationClass);
102: putProperty(PROP_REPRESENTATION_CLASS_NAME, representationClass
103: .getName());
104: if (representationClass.getClassLoader() == getClass()
105: .getClassLoader()) {
106: ERR.warning("Use of super(" + representationClass.getName()
107: + ".class) in " + getClass().getName()
108: + "() should be replaced with super(\""
109: + representationClass.getName()
110: + "\") to reduce unnecessary class loading");
111: }
112: }
113:
114: /** Create a new data loader.
115: * Pass its representation class name
116: * as a parameter to the constructor. The constructor is then allowed
117: * to return only subclasses of the representation class as the result of
118: * {@link #findDataObject}.
119: *
120: * @param representationClassName the name of the superclass for all objects
121: * returned from
122: * {@link #findDataObject}. The class may be anything but
123: * should be chosen to be as close as possible to the actual class of objects returned from the loader,
124: * to best identify the loader's data objects to listeners.
125: *
126: * @since 1.10
127: */
128: protected DataLoader(String representationClassName) {
129: putProperty(PROP_REPRESENTATION_CLASS_NAME,
130: representationClassName);
131: // ensure the provided name is correct and can be loaded
132: assert getRepresentationClass() != null;
133: }
134:
135: /**
136: * Get the representation class for this data loader, as passed to the constructor.
137: * @return the representation class
138: */
139: public final Class<? extends DataObject> getRepresentationClass() {
140: Class<?> _cls = (Class<?>) getProperty(PROP_REPRESENTATION_CLASS);
141: if (_cls != null) {
142: return _cls.asSubclass(DataObject.class);
143: }
144:
145: Class<? extends DataObject> cls;
146: String clsName = (String) getProperty(PROP_REPRESENTATION_CLASS_NAME);
147: try {
148: cls = Class.forName(clsName, false,
149: getClass().getClassLoader()).asSubclass(
150: DataObject.class);
151: } catch (ClassNotFoundException cnfe) {
152: throw (IllegalStateException) new IllegalStateException(
153: "Failed to load " + clsName + " from "
154: + getClass().getClassLoader())
155: .initCause(cnfe);
156: }
157:
158: putProperty(PROP_REPRESENTATION_CLASS, cls);
159: return cls;
160: }
161:
162: /**
163: * Get the name of the representation class for this data loader.
164: * Might avoid actually loading the class.
165: * @return the class name
166: * @see #getRepresentationClass
167: * @since 3.25
168: */
169: public final String getRepresentationClassName() {
170: return (String) getProperty(PROP_REPRESENTATION_CLASS_NAME);
171: }
172:
173: /** Get actions.
174: * These actions are used to compose
175: * a popup menu for the data object. Also these actions should
176: * be customizable by the user, so he can modify the popup menu on a
177: * data object.
178: *
179: * @return array of system actions or <CODE>null</CODE> if this loader does not have any
180: * actions
181: */
182: public final SystemAction[] getActions() {
183: Action[] arr = getSwingActions();
184:
185: List<SystemAction> list = new ArrayList<SystemAction>();
186: for (int i = 0; i < arr.length; i++) {
187: if (arr[i] instanceof SystemAction || arr[i] == null) {
188: list.add((SystemAction) arr[i]);
189: }
190: }
191:
192: return list.toArray(new SystemAction[list.size()]);
193: }
194:
195: /** Swing actions getter, used from DataNode */
196: final Action[] getSwingActions() {
197: DataLdrActions mgr = findManager();
198: if (mgr != null) {
199: Action[] actions;
200: try {
201: actions = (Action[]) mgr.instanceCreate();
202: } catch (IOException ex) {
203: Exceptions.printStackTrace(ex);
204: actions = null;
205: } catch (ClassNotFoundException ex) {
206: Exceptions.printStackTrace(ex);
207: actions = null;
208: }
209: if (actions == null) {
210: return new Action[0];
211: }
212:
213: return actions;
214: } else {
215: // old behaviour, that stores actions in properties
216: SystemAction[] actions = (SystemAction[]) getProperty(PROP_ACTIONS);
217: if (actions == null) {
218: actions = (SystemAction[]) getProperty(PROP_DEF_ACTIONS);
219: if (actions == null) {
220: actions = defaultActions();
221: putProperty(PROP_DEF_ACTIONS, actions, false);
222: }
223: }
224: return actions;
225: }
226: }
227:
228: /** Identifies the name of context in layer files where the
229: * loader wishes to store its own actions and also read them.
230: * In principle any {@link javax.swing.Action} instance can be registered
231: * in the context and it will be visible in the default DataNode
232: * for data object created by this loader. Only SystemAction can however
233: * be manipulated from DataLoader getActions/setActions methods.
234: * <p>
235: * The default implementation returns null to indicate that no
236: * layer reading should be used (use {@link #defaultActions} instead).
237: * <p>
238: * {@link javax.swing.JSeparator} instances may be used to separate items.
239: * <p>
240: * Suggested context name: <samp>Loaders/<em>PRIMARY-FILE/MIME-TYPE</em>/Actions</samp>
241: *
242: * @return the string name of the context on layer files to read/write actions to
243: * @since 5.0
244: */
245: protected String actionsContext() {
246: return null;
247: }
248:
249: /**
250: * Get default actions. Now deprecated;
251: * instead of overriding this method it
252: * is preferable to override {@link #actionsContext}.
253: * @return array of default system actions
254: */
255: protected SystemAction[] defaultActions() {
256: SystemAction[] actions = NodeOp.getDefaultActions();
257: return actions;
258: }
259:
260: /** Actions manager.
261: */
262: private final DataLdrActions findManager() {
263: Object manager = getProperty(ACTION_MANAGER);
264: if (manager instanceof Class) {
265: return null;
266: }
267: DataLdrActions mgr = (DataLdrActions) manager;
268: boolean newlyCreated = false;
269: if (mgr == null) {
270: String context = actionsContext();
271: if (context == null) {
272: // mark we have no context
273: putProperty(ACTION_MANAGER, getClass());
274: return null;
275: }
276:
277: FileObject fo = Repository.getDefault()
278: .getDefaultFileSystem().findResource(context);
279: if (fo == null) {
280: fo = Repository.getDefault().getDefaultFileSystem()
281: .getRoot();
282: try {
283: fo = FileUtil.createFolder(fo, context);
284:
285: } catch (IOException ex) {
286: ERR.log(Level.WARNING, null, ex);
287: }
288: newlyCreated = true;
289: }
290:
291: mgr = new DataLdrActions(DataFolder.findFolder(fo), this );
292: if (newlyCreated) {
293: SystemAction[] arr = defaultActions();
294: if (arr != null) {
295: mgr.setActions(arr);
296: }
297: }
298: putProperty(ACTION_MANAGER, mgr);
299: }
300: return mgr;
301: }
302:
303: /** Allows the friend code (package and tests) to wait while actions
304: * are synchronized with the state of disk.
305: */
306: final void waitForActions() {
307: DataLdrActions mgr = findManager();
308: if (mgr != null) {
309: mgr.waitFinished();
310: }
311: }
312:
313: /** Set actions.
314: * <p>Note that this method is public, not protected, so it is possible for anyone
315: * to modify the loader's popup actions externally (after finding the loader
316: * using {@link DataLoaderPool#firstProducerOf}).
317: * While this is possible, anyone doing so must take care to place new actions
318: * into sensible positions, including consideration of separators.
319: * This may also adversely affect the intended feel of the data objects.
320: * A preferable solution is generally to use {@link org.openide.actions.ToolsAction service actions}.
321: * @param actions actions for this loader or <CODE>null</CODE> if it should not have any
322: * @see #getActions
323: */
324: public final void setActions(SystemAction[] actions) {
325: DataLdrActions mgr = findManager();
326: if (mgr != null) {
327: mgr.setActions(actions);
328: } else {
329: putProperty(PROP_ACTIONS, actions, true);
330: }
331: }
332:
333: /** Assigns this loader new array of swing actions.
334: * @param arr List<Action>
335: */
336: final void setSwingActions(List/*<Action>*/arr) {
337: firePropertyChange(PROP_ACTIONS, null, null);
338: }
339:
340: /** Get the current display name of this loader.
341: * @return display name
342: */
343: public final String getDisplayName() {
344: String dn = (String) getProperty(PROP_DISPLAY_NAME);
345: if (dn != null) {
346: return dn;
347: } else {
348: dn = defaultDisplayName();
349: if (dn != null) {
350: return dn;
351: } else {
352: return getRepresentationClassName();
353: }
354: }
355: }
356:
357: /** Set the display name for this loader. Only subclasses should set the name.
358: * @param displayName new name
359: */
360: protected final void setDisplayName(final String displayName) {
361: putProperty(PROP_DISPLAY_NAME, displayName, true);
362: }
363:
364: /** Get the default display name of this loader.
365: * @return default display name
366: */
367: protected String defaultDisplayName() {
368: return NbBundle.getBundle(DataLoader.class).getString(
369: "LBL_loader_display_name");
370: }
371:
372: /** Find a data object appropriate to the given file object--the meat of this class.
373: * <p>
374: * For example: for files with the same basename but extensions <EM>.java</EM> and <EM>.class</EM>, the handler
375: * should return the same <code>DataObject</code>.
376: * <P>
377: * The loader can add all files it has recognized into the <CODE>recognized</CODE>
378: * buffer. Then all these files will be excluded from further processing.
379: *
380: * @param fo file object to recognize
381: * @param recognized recognized file buffer
382: * @exception DataObjectExistsException if the data object for the
383: * primary file already exists
384: * @exception IOException if the object is recognized but cannot be created
385: * @exception InvalidClassException if the class is not instance of
386: * {@link #getRepresentationClass}
387: *
388: * @return suitable data object or <CODE>null</CODE> if the handler cannot
389: * recognize this object (or its group)
390: * @see #handleFindDataObject
391: */
392: public final DataObject findDataObject(FileObject fo,
393: RecognizedFiles recognized) throws IOException {
394: try {
395: return DataObjectPool.handleFindDataObject(this , fo,
396: recognized);
397: } catch (IOException ioe) {
398: throw ioe;
399: } catch (ThreadDeath td) {
400: throw td;
401: } catch (RuntimeException e) {
402: if (e.getClass().getName().startsWith(
403: "org.openide.util.lookup")) { // NOI18N
404: // to propagate
405: // org.openide.util.lookup.AbstractLookup$ISE: You are trying to modify lookup from lookup query!
406: throw e;
407: }
408: // Some strange error, perhaps an unexpected exception in
409: // MultiFileLoader.findPrimaryFile. Such an error ought
410: // not cause whole folder recognizer to die! Assume that
411: // file/loader is kaput and continue.
412: IOException ioe = new IOException(e.toString());
413: Logger.getLogger(DataLoader.class.getName()).log(
414: Level.WARNING, null, e);
415: ioe.initCause(e);
416: throw ioe;
417: }
418:
419: /*
420: if (obj != null && !getRepresentationClass ().isInstance (obj)) {
421: // does not fullfil representation class
422: throw new java.io.InvalidClassException (obj.getClass ().toString ());
423: }
424:
425: return obj;
426: */
427: }
428:
429: /** Find a data object appropriate to the given file object (as implemented in subclasses).
430: * @see #findDataObject
431: * @param fo file object to recognize
432: * @param recognized recognized file buffer
433: * @exception DataObjectExistsException as in <code>#findDataObject</code>
434: * @exception IOException as in <code>#findDataObject</code>
435: *
436: * @return the data object or <code>null</code>
437: */
438: protected abstract DataObject handleFindDataObject(FileObject fo,
439: RecognizedFiles recognized) throws IOException;
440:
441: /** Utility method to mark a file as belonging to this loader.
442: * When the file is to be recognized this loader will be used first.
443: * <P>
444: * This method is used by {@link DataObject#markFiles}.
445: *
446: * @param fo file to mark
447: * @exception IOException if setting the file's attribute failed
448: */
449: public final void markFile(FileObject fo) throws IOException {
450: DataLoaderPool.setPreferredLoader(fo, this );
451: }
452:
453: /** Writes nothing to the stream.
454: * @param oo ignored
455: */
456: public void writeExternal(ObjectOutput oo) throws IOException {
457: oo.writeObject(new Integer(LOADER_VERSION));
458:
459: SystemAction[] arr = (SystemAction[]) getProperty(PROP_ACTIONS);
460: if (arr == null) {
461: oo.writeObject(null);
462: } else {
463: // convert actions to class names
464: List<String> names = new LinkedList<String>();
465: for (int i = 0; i < arr.length; i++) {
466: if (arr[i] == null) {
467: names.add(null);
468: } else {
469: names.add(arr[i].getClass().getName());
470: }
471: }
472: oo.writeObject(names.toArray());
473: }
474:
475: String dn = (String) getProperty(PROP_DISPLAY_NAME);
476: if (dn == null)
477: dn = ""; // NOI18N
478: oo.writeUTF(dn);
479: }
480:
481: /** Reads actions and display name from the stream.
482: * @param oi input source to read from
483: * @exception SafeException if some of the actions is not found in the
484: * stream, but all the content has been read ok. Subclasses can
485: * catch this exception and continue reading from the stream
486: */
487: public void readExternal(ObjectInput oi) throws IOException,
488: ClassNotFoundException {
489: Exception main = null;
490: int version = 0;
491:
492: Object first = oi.readObject();
493: if (first instanceof Integer) {
494: version = ((Integer) first).intValue();
495: first = oi.readObject();
496: }
497: // new version that reads the names of the actions - NB3.1
498: Object[] arr = (Object[]) first;
499: boolean isdefault = true;
500:
501: SystemAction[] defactions = getActions();
502:
503: if (version > 0
504: || (version == 0 && arr.length != defactions.length))
505: isdefault = false;
506: if (arr != null) {
507: List<SystemAction> ll = new ArrayList<SystemAction>(
508: arr.length);
509: for (int i = 0; i < arr.length; i++) {
510: if (arr[i] == null) {
511: ll.add(null);
512: if (version == 0 && isdefault
513: && defactions[i] != null)
514: isdefault = false;
515: continue;
516: }
517:
518: try {
519: ClassLoader loader = Lookup.getDefault().lookup(
520: ClassLoader.class);
521: if (loader == null) {
522: loader = getClass().getClassLoader();
523: }
524: Class<? extends SystemAction> c = Class.forName(
525: Utilities.translate((String) arr[i]),
526: false, // why resolve?? --jglick
527: loader).asSubclass(SystemAction.class);
528: SystemAction ac = SystemAction.get(c);
529:
530: ll.add(ac);
531: if (version == 0 && isdefault
532: && !defactions[i].equals(ac))
533: isdefault = false;
534: } catch (ClassNotFoundException ex) {
535: if (main == null) {
536: main = ex;
537: } else {
538: Throwable t = main;
539: while (t.getCause() != null) {
540: t = t.getCause();
541: }
542: t.initCause(ex);
543: }
544: }
545: }
546: if (main == null && !isdefault) {
547: // Whole action list was successfully read.
548: setActions(ll.toArray(new SystemAction[ll.size()]));
549: } // Else do not try to override the default action list if it is incomplete anyway.
550: }
551:
552: String displayName = oi.readUTF();
553: if (displayName.equals("")
554: || (version == 0 && displayName
555: .equals(defaultDisplayName()))) // NOI18N
556: displayName = null;
557: setDisplayName(displayName);
558:
559: if (main != null) {
560: // exception occured during reading
561: SafeException se = new SafeException(main);
562: // Provide a localized message explaining that there is no big problem.
563: String message = NbBundle.getMessage(DataLoader.class,
564: "EXC_missing_actions_in_loader", getDisplayName());
565: Exceptions.attachLocalizedMessage(se, message);
566: throw se;
567: }
568: }
569:
570: protected boolean clearSharedData() {
571: return false;
572: }
573:
574: /** Get a registered loader from the pool.
575: * @param loaderClass exact class of the loader (<em>not</em> its data object representation class)
576: * @return the loader instance, or <code>null</code> if there is no such loader registered
577: * @see DataLoaderPool#allLoaders
578: */
579: public static <T extends DataLoader> T getLoader(
580: Class<T> loaderClass) {
581: return findObject(loaderClass, true);
582: }
583:
584: // XXX huh? --jglick
585: // The parameter can be <CODE>null</CODE> to
586: // simplify testing whether the file object fo is valid or not
587: /** Buffer holding a list of primary and secondary files marked as already recognized, to prevent further scanning.
588: */
589: public interface RecognizedFiles {
590: /** Mark this file as being recognized. It will be excluded
591: * from further processing.
592: *
593: * @param fo file object to exclude
594: */
595: public void markRecognized(FileObject fo);
596: }
597:
598: }
|