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.properties;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.ObjectInputStream;
047: import java.io.IOException;
048: import java.util.ArrayList;
049: import java.util.Comparator;
050: import java.util.Iterator;
051: import java.util.TreeSet;
052: import java.util.logging.Logger;
053: import org.openide.cookies.SaveCookie;
054:
055: import org.openide.filesystems.FileObject;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.loaders.DataFolder;
058: import org.openide.loaders.DataNode;
059: import org.openide.loaders.DataObject;
060: import org.openide.loaders.DataObjectExistsException;
061: import org.openide.loaders.MultiDataObject;
062: import org.openide.nodes.Children;
063: import org.openide.nodes.CookieSet;
064: import org.openide.nodes.Node;
065: import org.openide.util.Lookup;
066: import org.openide.util.WeakListeners;
067: import org.openide.util.lookup.Lookups;
068: import org.openide.util.lookup.ProxyLookup;
069: import static java.util.logging.Level.FINER;
070:
071: /**
072: * Object that provides main functionality for properties data loader.
073: * Represents set of .properties files with same basic name (name without locale postfix).
074: *
075: * @author Ian Formanek
076: */
077: public final class PropertiesDataObject extends MultiDataObject
078: implements CookieSet.Factory {
079:
080: /** Generated Serialized Version UID. */
081: static final long serialVersionUID = 4795737295255253334L;
082:
083: static final Logger LOG = Logger
084: .getLogger(PropertiesDataObject.class.getName());
085:
086: /** Structural view of the dataobject */
087: private transient BundleStructure bundleStructure;
088:
089: /** Open support for this data object. Provides editable table view on bundle. */
090: private transient PropertiesOpen openSupport;
091:
092: /** Lock used for synchronization of <code>openSupport</code> instance creation */
093: private final transient Object OPEN_SUPPORT_LOCK = new Object();
094:
095: // Hack due having lock on secondaries, can't override handleCopy, handleMove at all.
096: /** Suffix used by copying/moving dataObject. */
097: private transient String pasteSuffix;
098:
099: /** */
100: private Lookup lookup;
101:
102: /**
103: * Constructs a <code>PropertiesDataObject</code> for a specified
104: * primary file.
105: *
106: * @param primaryFile primary file to creata a data object for
107: * @param loader data loader which recognized the primary file
108: * @exception org.openide.loaders.DataObjectExistsException
109: * if another <code>DataObject</code> already exists
110: * for the specified file
111: */
112: public PropertiesDataObject(final FileObject primaryFile,
113: final PropertiesDataLoader loader)
114: throws DataObjectExistsException {
115: super (primaryFile, loader);
116: // use editor support
117: initialize();
118: }
119:
120: /**
121: */
122: PropertiesEncoding getEncoding() {
123: return ((PropertiesDataLoader) getLoader()).getEncoding();
124: }
125:
126: private Lookup getSuperLookup() {
127: return super .getLookup();
128: }
129:
130: @Override
131: public Lookup getLookup() {
132: if (lookup == null) {
133: lookup = new ProxyLookup(Lookups.singleton(getEncoding()),
134: Lookups.proxy(new Lookup.Provider() {
135: public Lookup getLookup() {
136: return getSuperLookup();
137: }
138: }));
139: }
140: return lookup;
141: }
142:
143: /** Initializes the object. Used by construction and deserialized. */
144: private void initialize() {
145: bundleStructure = null;
146: Class<? extends Node.Cookie>[] arr = (Class<Node.Cookie>[]) new Class[2];
147: arr[0] = PropertiesOpen.class;
148: arr[1] = PropertiesEditorSupport.class;
149: getCookieSet().add(arr, this );
150: }
151:
152: /** Implements <code>CookieSet.Factory</code> interface method. */
153: @SuppressWarnings("unchecked")
154: public <T extends Node.Cookie> T createCookie(Class<T> clazz) {
155: if (clazz.isAssignableFrom(PropertiesOpen.class)) {
156: return (T) getOpenSupport();
157: } else if (clazz
158: .isAssignableFrom(PropertiesEditorSupport.class)) {
159: return (T) ((PropertiesFileEntry) getPrimaryEntry())
160: .getPropertiesEditor();
161: } else {
162: return null;
163: }
164: }
165:
166: // Accessibility from PropertiesOpen:
167: CookieSet getCookieSet0() {
168: return getCookieSet();
169: }
170:
171: /** Copies primary and secondary files to new folder.
172: * Overrides superclass method.
173: * @param df the new folder
174: * @return data object for the new primary
175: * @throws IOException if there was a problem copying
176: * @throws UserCancelException if the user cancelled the copy */
177: @Override
178: protected synchronized DataObject handleCopy(DataFolder df)
179: throws IOException {
180: if (LOG.isLoggable(FINER)) {
181: LOG.finer("handleCopy(" //NOI18N
182: + FileUtil.getFileDisplayName(df.getPrimaryFile())
183: + ')');
184: }
185: try {
186: pasteSuffix = createPasteSuffix(df);
187:
188: return super .handleCopy(df);
189: } finally {
190: pasteSuffix = null;
191: }
192: }
193:
194: /** Moves primary and secondary files to a new folder.
195: * Overrides superclass method.
196: * @param df the new folder
197: * @return the moved primary file object
198: * @throws IOException if there was a problem moving
199: * @throws UserCancelException if the user cancelled the move */
200: @Override
201: protected FileObject handleMove(DataFolder df) throws IOException {
202: if (LOG.isLoggable(FINER)) {
203: LOG.finer("handleMove(" //NOI18N
204: + FileUtil.getFileDisplayName(df.getPrimaryFile())
205: + ')');
206: }
207:
208: // a simple fix of issue #92195 (impossible to save a moved prop. file):
209: SaveCookie saveCookie = getCookie(SaveCookie.class);
210: if (saveCookie != null) {
211: saveCookie.save();
212: }
213:
214: try {
215: pasteSuffix = createPasteSuffix(df);
216:
217: return super .handleMove(df);
218: } finally {
219: pasteSuffix = null;
220: }
221: }
222:
223: /** Gets suffix used by entries by copying/moving. */
224: String getPasteSuffix() {
225: return pasteSuffix;
226: }
227:
228: /** Only accessible method, it is necessary to call MultiDataObject's method
229: * from this package.
230: */
231: void removeSecondaryEntry2(Entry fe) {
232: if (LOG.isLoggable(FINER)) {
233: LOG.finer("removeSecondaryEntry2(Entry " //NOI18N
234: + FileUtil.getFileDisplayName(fe.getFile()) + ')');
235: }
236: removeSecondaryEntry(fe);
237: }
238:
239: /** Creates new name for this instance when moving/copying to new folder destination.
240: * @param folder new folder destination. */
241: private String createPasteSuffix(DataFolder folder) {
242: String basicName = getPrimaryFile().getName();
243:
244: DataObject[] children = folder.getChildren();
245:
246: // Repeat until there is not such file name.
247: for (int i = 0;; i++) {
248: String newName;
249:
250: if (i == 0) {
251: newName = basicName;
252: } else {
253: newName = basicName + i;
254: }
255: boolean exist = false;
256:
257: for (int j = 0; j < children.length; j++) {
258: if (children[j] instanceof PropertiesDataObject
259: && newName.equals(children[j].getName())) {
260: exist = true;
261: break;
262: }
263: }
264:
265: if (!exist) {
266: if (i == 0) {
267: return ""; // NOI18N
268: } else {
269: return "" + i; // NOI18N
270: }
271: }
272: }
273: }
274:
275: /** Returns open support. It's used by all subentries as open support too. */
276: public PropertiesOpen getOpenSupport() {
277: synchronized (OPEN_SUPPORT_LOCK) {
278: if (openSupport == null) {
279: openSupport = new PropertiesOpen(this );
280: }
281:
282: return openSupport;
283: }
284: }
285:
286: /** Updates modification status of this dataobject from its entries. */
287: void updateModificationStatus() {
288: LOG.finer("updateModificationStatus()"); //NOI18N
289: boolean modif = false;
290: if (((PresentableFileEntry) getPrimaryEntry()).isModified())
291: modif = true;
292: else {
293: for (Iterator it = secondaryEntries().iterator(); it
294: .hasNext();) {
295: if (((PresentableFileEntry) it.next()).isModified()) {
296: modif = true;
297: break;
298: }
299: }
300: }
301:
302: super .setModified(modif);
303: }
304:
305: /** Provides node that should represent this data object. When a node for representation
306: * in a parent is requested by a call to getNode (parent) it is the exact copy of this node
307: * with only parent changed. This implementation creates instance
308: * <CODE>DataNode</CODE>.
309: * <P>
310: * This method is called only once.
311: *
312: * @return the node representation for this data object
313: * @see DataNode
314: */
315: @Override
316: protected Node createNodeDelegate() {
317: PropertiesChildren pc = new PropertiesChildren();
318:
319: // properties node - creates new types
320: DataNode dn = new PropertiesDataNode(this , pc);
321: return dn;
322: }
323:
324: /** Returns a structural view of this data object */
325: public BundleStructure getBundleStructure() {
326: if (bundleStructure == null)
327: bundleStructure = new BundleStructure(this );
328: return bundleStructure;
329: }
330:
331: /** Comparator used for ordering secondary files, works over file names */
332: public static Comparator<String> getSecondaryFilesComparator() {
333: return new KeyComparator();
334: }
335:
336: /**
337: */
338: void fireNameChange() {
339: LOG.finer("fireNameChange()"); //NOI18N
340: firePropertyChange(PROP_NAME, null, null);
341: }
342:
343: /** Deserialization. */
344: private void readObject(ObjectInputStream in) throws IOException,
345: ClassNotFoundException {
346: in.defaultReadObject();
347: initialize();
348: }
349:
350: /** Children of this <code>PropertiesDataObject</code>. */
351: private class PropertiesChildren extends Children.Keys<String> {
352:
353: /** Listens to changes on the dataobject */
354: private PropertyChangeListener propertyListener = null;
355:
356: /** Constructor.*/
357: PropertiesChildren() {
358: super ();
359: }
360:
361: /** Sets all keys in the correct order */
362: protected void mySetKeys() {
363: TreeSet<String> newKeys = new TreeSet<String>(
364: new Comparator<String>() {
365: public int compare(String o1, String o2) {
366: if (o1 == o2) {
367: return 0;
368: }
369: if (o1 == null) {
370: return -1;
371: }
372: if (o2 == null) {
373: return 1;
374: }
375: return o1.compareTo(o2);
376: }
377: });
378:
379: newKeys.add(getPrimaryEntry().getFile().getName());
380:
381: for (Entry entry : secondaryEntries()) {
382: newKeys.add(entry.getFile().getName());
383: }
384:
385: setKeys(newKeys);
386: }
387:
388: /** Called to notify that the children has been asked for children
389: * after and that they should set its keys. Overrides superclass method. */
390: @Override
391: protected void addNotify() {
392: mySetKeys();
393:
394: // listener
395: if (propertyListener == null) {
396: propertyListener = new PropertyChangeListener() {
397: public void propertyChange(PropertyChangeEvent evt) {
398: if (PROP_FILES.equals(evt.getPropertyName())) {
399: mySetKeys();
400: }
401: }
402: };
403:
404: PropertiesDataObject.this
405: .addPropertyChangeListener(WeakListeners
406: .propertyChange(propertyListener,
407: PropertiesDataObject.this ));
408: }
409: }
410:
411: /** Called to notify that the children has lost all of its references to
412: * its nodes associated to keys and that the keys could be cleared without
413: * affecting any nodes (because nobody listens to that nodes).
414: * Overrides superclass method. */
415: @Override
416: protected void removeNotify() {
417: setKeys(new ArrayList<String>());
418: }
419:
420: /** Creates nodes for specified key. Implements superclass abstract method. */
421: protected Node[] createNodes(String entryName) {
422: if (entryName == null) {
423: return null;
424: }
425:
426: PropertiesFileEntry entry = (PropertiesFileEntry) getPrimaryEntry();
427:
428: if (entryName.equals(entry.getFile().getName())) {
429: return new Node[] { entry.getNodeDelegate() };
430: }
431: for (Iterator<Entry> it = secondaryEntries().iterator(); it
432: .hasNext();) {
433: entry = (PropertiesFileEntry) it.next();
434:
435: if (entryName.equals(entry.getFile().getName())) {
436: return new Node[] { entry.getNodeDelegate() };
437: }
438: }
439:
440: return null;
441: }
442:
443: } // End of class PropertiesChildren.
444:
445: }
|