001: /*
002: * The contents of this file are subject to the Mozilla Public License
003: * Version 1.1 (the "License"); you may not use this file except in
004: * compliance with the License. You may obtain a copy of the License at
005: * http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
009: * License for the specific language governing rights and limitations
010: * under the License.
011: *
012: * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
013: *
014: * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
015: * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
016: *
017: * Contributor(s):
018: * Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
019: *
020: * If you didn't download this code from the following link, you should check
021: * if you aren't using an obsolete version: http://www.isqlviewer.com
022: */
023: package org.isqlviewer.bookmarks;
024:
025: import java.io.Serializable;
026: import java.util.Comparator;
027: import java.util.Enumeration;
028: import java.util.Vector;
029:
030: import org.isqlviewer.util.BasicUtilities;
031: import org.isqlviewer.util.LocalMessages;
032:
033: /**
034: * Folder component for representing a collection of bookmarks and bookmark folders.
035: * <p>
036: * This object is the basis of creating a bookmark tree. This component is modeled loosly after the java.io.File object
037: * since bookmarks are organized in similar fashion to files.
038: *
039: * @author Mark A. Kobold <mkobold at isqlviewer dot com>
040: * @version 1.0
041: */
042: public class BookmarkFolder implements Serializable {
043:
044: private static final long serialVersionUID = -5334474850329646918L;
045: /**
046: * This bookmark folder is the root of the folder tree, and this folder has no parent.
047: * <p>
048: *
049: * @see #getType()
050: */
051: public static final int TYPE_ROOT = 0;
052: /**
053: * This bookmark folder is folder that has dynamic contents based on other variables.
054: * <p>
055: *
056: * @see #getType()
057: */
058: public static final int TYPE_SMART = 1;
059: /**
060: * This bookmark folder is the regular folder that has no special function.
061: * <p>
062: *
063: * @see #getType()
064: */
065: public static final int TYPE_NORMAL = 2;
066:
067: private static final String RESOURCE_BUNDLE = "org.isqlviewer.bookmarks.ResourceBundle";
068: private static final LocalMessages messages = new LocalMessages(
069: RESOURCE_BUNDLE);
070: // sorter that ensures that folders are first then bookmarks, both are sorted lexigraphically by name.
071: private transient final Comparator<Object> sorter = new FolderSorter();
072: // one of the specific TYPE_XXX for the purpose of this folder.
073: private int type = TYPE_NORMAL;
074: // user specified name for this folder.
075: private String name = null;
076: // parent folder should only be null if the type is root and vice-versa.
077: private BookmarkFolder parentFolder = null;
078: // collection of child elements it is mix of Bookmarks and Bookmark Folders.
079: private Vector<Object> children = new Vector<Object>();
080: // path name for this folder.
081: private String path = null;
082:
083: /**
084: * Creates a new root folder for creating a new bookmark tree.
085: * <p>
086: *
087: * @return new bookmark folder that has a type of {@link #TYPE_ROOT}.
088: */
089: public static BookmarkFolder createRootFolder() {
090:
091: return new BookmarkFolder("/", TYPE_ROOT);
092: }
093:
094: public static BookmarkFolder[] getPathElements(BookmarkFolder folder) {
095:
096: Vector<BookmarkFolder> elements = new Vector<BookmarkFolder>();
097: elements.add(folder);
098: BookmarkFolder parentFolder = folder;
099: while ((parentFolder = parentFolder.parentFolder) != null) {
100: elements.insertElementAt(parentFolder, 0);
101: }
102: return elements.toArray(new BookmarkFolder[elements.size()]);
103: }
104:
105: /**
106: * Creates a new bookmark using the specified parent.
107: * <p>
108: * The folder created by this method will already be added to the parent folder.
109: *
110: * @param folderName of this new folder.
111: * @throws IllegalArgumentException is null parentFolder is given.
112: */
113: public BookmarkFolder(String folderName) {
114:
115: this (folderName, TYPE_NORMAL);
116: }
117:
118: /**
119: * Constructor for creating new instance of a bookmark.
120: * <p>
121: *
122: * @param folder parent folder for this folder instance if any.
123: * @param type the type of folder it actually is.
124: * @throws IllegalArgumentException if the type is not root and foler is null.
125: */
126: BookmarkFolder(String folderName, int type) {
127:
128: if (type != TYPE_ROOT && folderName == null) {
129: throw new IllegalArgumentException(messages.format(""));
130: }
131: this .name = folderName;
132: this .type = type;
133: calculatePath();
134: }
135:
136: /**
137: * Constructor for creating new instance of a bookmark.
138: * <p>
139: *
140: * @param folder parent folder for this folder instance if any.
141: * @param type the type of folder it actually is.
142: * @throws IllegalArgumentException if the type is not root and foler is null.
143: */
144: BookmarkFolder(BookmarkFolder parentFolder, String folderName) {
145:
146: if (type != TYPE_ROOT && folderName == null) {
147: throw new IllegalArgumentException(messages.format(""));
148: }
149: this .name = folderName;
150: this .parentFolder = parentFolder;
151: calculatePath();
152: }
153:
154: @Override
155: /**
156: * Returns the full path of this bookmark folder.
157: * <p>
158: *
159: * @return path for this folder.
160: */
161: public String toString() {
162:
163: return name;
164: }
165:
166: /**
167: * Gets the user-specific name for this folder.
168: * <p>
169: * This value can be null if no name has been set. if this folder is a root folder a simple '/' will be returned.
170: *
171: * @return returns the name of this folder.
172: * @see #setName(String)
173: */
174: public String getName() {
175:
176: return type == TYPE_ROOT ? path : name;
177: }
178:
179: /**
180: * Gets the name of the parent folder for this folder.
181: * <p>
182: * If this is a root folder than the folder will null.
183: *
184: * @return name of the parent folder.
185: */
186: public String getParent() {
187:
188: return type == TYPE_ROOT ? null : parentFolder.getName();
189: }
190:
191: /**
192: * Gets the full path of this folder.
193: * <p>
194: * The path will be in a *NIX style convention of '/parent/child/folder/'
195: *
196: * @return the path represented by this folder.
197: */
198: public String getPath() {
199:
200: return path;
201: }
202:
203: /**
204: * Returns a string list of all the names of the folders and bookmarks in this folder.
205: * <p>
206: * This will return a list of all folders and bookmark names contained in this folder. All folders will be first in
207: * the list and will be prefixed with '/'.
208: *
209: * @return string list of the names of all components in this folder.
210: */
211: public String[] list() {
212:
213: synchronized (children) {
214: String[] names = new String[children.size()];
215: for (int i = 0; i < names.length; i++) {
216: Object o = children.get(i);
217: if (o instanceof BookmarkFolder) {
218: names[i] = '/' + ((BookmarkFolder) o).name;
219: } else if (o instanceof Bookmark) {
220: names[i] = ((Bookmark) o).getName();
221: }
222: }
223: return names;
224: }
225: }
226:
227: /**
228: * Searches for a folder using the specified path.
229: * <p>
230: * Assumes that the given path precedes the given path.
231: *
232: * @param childPath to search for within this folder.
233: * @return folder based on the path, can be <tt>null</tt> if child path does not exist.
234: */
235: public BookmarkFolder findChildPath(String childPath) {
236:
237: if (childPath == null || childPath.trim().length() == 0) {
238: return null;
239: }
240:
241: synchronized (children) {
242: String childName = null;
243: String subPath = null;
244: if (childPath.charAt(0) == '/') {
245: int next = childPath.indexOf('/', 1);
246: if (next < 0) {
247: childName = childPath.substring(1);
248: subPath = childName;
249: } else {
250: childName = childPath.substring(1, next);
251: subPath = childPath.substring(next);
252: }
253: } else {
254: childName = childPath.substring(0, childPath.indexOf(
255: '/', 0));
256: subPath = childPath.substring(childName.length());
257: }
258: if (childName.trim().length() == 0) {
259: return this ;
260: }
261: for (int i = 0; i < children.size(); i++) {
262: Object o = children.get(i);
263: if (o instanceof BookmarkFolder) {
264: if (childName.equals(((BookmarkFolder) o).name)) {
265: if (subPath.indexOf('/') >= 0) {
266: return ((BookmarkFolder) o)
267: .findChildPath(subPath);
268: }
269: return (BookmarkFolder) o;
270: }
271: }
272: }
273: return null;
274: }
275: }
276:
277: /**
278: * @param string
279: * @return
280: */
281: public BookmarkFolder mkdirs(String childPath) {
282:
283: if (childPath == null || childPath.trim().length() == 0) {
284: return null;
285: } else if (path.equalsIgnoreCase(childPath)) {
286: return this ;
287: }
288:
289: synchronized (children) {
290: String childName = null;
291: String subPath = null;
292: if (childPath.charAt(0) == '/') {
293: int next = childPath.indexOf('/', 1);
294: if (next < 0) {
295: childName = childPath.substring(1);
296: subPath = childName;
297: } else {
298: childName = childPath.substring(1, next);
299: subPath = childPath.substring(next);
300: }
301: } else {
302: int slashIndex = childPath.indexOf('/', 0);
303: if (slashIndex >= 0) {
304: childName = childPath.substring(0, slashIndex);
305: subPath = childPath.substring(childName.length());
306: } else {
307: childName = "";
308: subPath = "";
309: }
310: }
311:
312: if (childName.trim().length() == 0) {
313: return this ;
314: }
315:
316: for (int i = 0; i < children.size(); i++) {
317: Object o = children.get(i);
318: if (o instanceof BookmarkFolder) {
319: if (childName.equals(((BookmarkFolder) o).name)) {
320: if (subPath.indexOf('/') >= 0) {
321: return ((BookmarkFolder) o).mkdirs(subPath);
322: }
323: return (BookmarkFolder) o;
324: }
325: }
326: }
327: BookmarkFolder childFolder = addChildFolder(childName);
328: return childFolder.mkdirs(subPath);
329: }
330: }
331:
332: /**
333: * Changes the name of this folder.
334: * <p>
335: * Once the name is changed the path name will also be changed.
336: *
337: * @param name of the folder to be set.
338: * @throws IllegalStateException if the type of folder is ROOT
339: * @throws IllegalArgumentException if the new name exists in the parent folder.
340: */
341: public void setName(String name) {
342:
343: if (type == TYPE_ROOT) {
344: throw new IllegalStateException(messages
345: .format("BookmarkFolder.RootRenameError"));
346: }
347: this .name = name;
348: calculatePath();
349: }
350:
351: /**
352: * Gets the parent folder this folder was created with.
353: * <p>
354: *
355: * @return the parent folder of this instance; will be null if this is ROOT folder.
356: */
357: public BookmarkFolder getParentFolder() {
358:
359: return parentFolder;
360: }
361:
362: /**
363: * Gets the type of folder this instance is.
364: * <p>
365: *
366: * @return one of the TYPE_XXX constant values defined in this class.
367: * @see #TYPE_NORMAL
368: * @see #TYPE_ROOT
369: * @see #TYPE_SMART
370: */
371: public int getType() {
372:
373: return type;
374: }
375:
376: /**
377: * Adds a child folder to this instance.
378: * <p>
379: * This method will essentially move the folder from another tree the child folder will be detached from existing
380: * parent and added to this parent.
381: * <p>
382: * An illegal argument exception will be thrown if one of the following restrictions are broken.
383: * <ul>
384: * <li>TYPE_NORMAL folders can only have other TYPE_NORMAL sub-folders</li>
385: * <li>TYPE_SMART folders can have TYPE_NORMAL or TYPE_SMART sub-folders</li>
386: * <li>TYPE_ROOT folders can have TYPE_NORMAL or TYPE_SMART sub-folders</li>
387: * </ul>
388: *
389: * @param childFolder to add to this instance.
390: * @throws NullPointerException if the child folder is null.
391: * @throws IllegalArgumentException if child folder has type that is logicall incorrect for this folder.
392: */
393: public BookmarkFolder addChildFolder(String folderName) {
394:
395: if (folderName == null) {
396: throw new NullPointerException(messages.format(
397: "BookmarkFolder.NullChildAddError", name));
398: }
399: synchronized (this ) {
400: BookmarkFolder childFolder = new BookmarkFolder(this ,
401: folderName);
402: children.add(childFolder);
403: BasicUtilities.sortCollection(children, sorter);
404: return childFolder;
405: }
406: }
407:
408: /**
409: * Removes a child folder from this instance.
410: * <p>
411: * Unlike the remove bookmark method this method cannot be called anywhere and the given folder must exist in this
412: * instance.
413: * <p>
414: * If a <tt>null</tt> bookmark is given this method immediately returns <tt>false</tt>.
415: *
416: * @param folder to remove from this instance.
417: * @return <tt>true</tt> if the child folder was successfully removed from this instance.
418: */
419: public boolean removeFolder(BookmarkFolder folder) {
420:
421: if (folder == null) {
422: return false;
423: }
424:
425: synchronized (this ) {
426: boolean wasRemoved = children.remove(folder);
427: if (wasRemoved) {
428: BasicUtilities.sortCollection(children, sorter);
429: }
430: return wasRemoved;
431: }
432: }
433:
434: /**
435: * Adds a bookmark to this folder.
436: * <p>
437: * The previous folder instance will no longer have the instance of the given bookmark after call.
438: *
439: * @param bookmark to be added to this folder.
440: * @throws NullPointerException if the child bookmark is null.
441: */
442: public void addBookmark(BookmarkReference bookmark) {
443:
444: if (bookmark == null) {
445: throw new NullPointerException(messages.format(
446: "BookmarkFolder.NullChildAddError", name));
447: }
448:
449: synchronized (bookmark) {
450: BookmarkFolder folder = bookmark.getFolder();
451: if (folder != null) {
452: synchronized (folder) {
453: folder.removeChild(bookmark);
454: }
455: }
456: synchronized (this ) {
457: children.add(bookmark);
458: BasicUtilities.sortCollection(children, sorter);
459: bookmark.setFolder(this );
460: }
461: }
462: }
463:
464: /**
465: * Removes a bookmark from the folder it resides within.
466: * <p>
467: * This method can technically be called on any folder, so the bookmark does not have to actually exist in this
468: * folder instance to be removed from its parent folder.
469: * <p>
470: * If a <tt>null</tt> bookmark is given this method immediately returns <tt>false</tt>.
471: *
472: * @param bookmark to remove from its parent folder.
473: * @return <tt>true</tt> if the bookmark was successfully removed from the folder.
474: */
475: public boolean removeBookmark(BookmarkReference bookmark) {
476:
477: if (bookmark == null) {
478: return false;
479: }
480:
481: BookmarkFolder folder = bookmark.getFolder();
482: synchronized (folder) {
483: return folder.removeChild(bookmark);
484: }
485: }
486:
487: /**
488: * Returns the index of the child component.
489: * <p>
490: * This method is here to mainly support the use of this object within the Swing TreeModel.
491: *
492: * @param child to get the index of.
493: * @return index of the child in the list of components; -1 if non-existent.
494: * @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object, java.lang.Object)
495: */
496: public int indexOfChild(Object child) {
497:
498: return children.indexOf(child);
499: }
500:
501: /**
502: * Gets the number of child components that exist within this instance.
503: * <p>
504: * This method is here to mainly support the use of this object within the Swing TreeModel.
505: *
506: * @return number of sub-folders and bookmarks that exist in this folder.
507: * @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
508: */
509: public int getChildCount() {
510:
511: return children.size();
512: }
513:
514: /**
515: * Enumeration for processing all child elements of this folder.
516: * <p>
517: * All objects return will be an instance of BookmarkFolder or Bookmark.
518: *
519: * @return enumeration of elements contained in this folder.
520: */
521: public Enumeration<Object> childElements() {
522:
523: return children.elements();
524: }
525:
526: /**
527: * Gets the child component by index.
528: * <p>
529: * This method is here to mainly support the use of this object within the Swing TreeModel.
530: *
531: * @param index of the child component to get.
532: * @return Bookmark or BookmarkFolder at the specified index, can be null if non-existent.
533: * @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
534: */
535: public Object getChild(int index) {
536:
537: return children.get(index);
538: }
539:
540: // removes the child component from this folder.
541: private boolean removeChild(Object child) {
542:
543: return children.remove(child);
544: }
545:
546: // quick patch calculation based on the path of the parent.
547: private void calculatePath() {
548:
549: StringBuffer pathBuffer = new StringBuffer("");
550: if (parentFolder != null) {
551: pathBuffer.append(parentFolder.path);
552: pathBuffer.append(name);
553: pathBuffer.append("/");
554: } else {
555: pathBuffer.append("/");
556: }
557:
558: path = pathBuffer.toString();
559: pathBuffer.setLength(0);
560: pathBuffer = null;
561: }
562:
563: /**
564: * Support comparator for ensuring that child elements of a folder are sorted correctly.
565: * <p>
566: * All child elements are sorted with bookmark-folders first then bookmarks. if two elements are of the same type
567: * then they are organized by the names of the respective objects.
568: *
569: * @author Mark A. Kobold <mkobold at isqlviewer dot com>
570: * @version 1.0
571: */
572: private static class FolderSorter implements Comparator<Object> {
573:
574: public int compare(Object o1, Object o2) {
575:
576: Class c1 = o1 == null ? Object.class : o1.getClass();
577: Class c2 = o2 == null ? Object.class : o2.getClass();
578: if (c1.equals(c2) && o1 != null & o2 != null) {
579: if (o1 instanceof BookmarkFolder) {
580: BookmarkFolder bf1 = (BookmarkFolder) o1;
581: BookmarkFolder bf2 = (BookmarkFolder) o2;
582: String n1 = bf1.name == null ? "" : bf1.name;
583: String n2 = bf2.name == null ? "" : bf2.name;
584: return n1.compareToIgnoreCase(n2);
585: } else if (o1 instanceof BookmarkReference) {
586: BookmarkReference b1 = (BookmarkReference) o1;
587: BookmarkReference b2 = (BookmarkReference) o2;
588: return b1.getName().compareToIgnoreCase(
589: b2.getName());
590: }
591: } else if (o1 != null && o2 != null) {
592: // not null but different types sort folder first //
593: return o1 instanceof BookmarkFolder ? -1 : 1;
594: }
595: return 0;
596: }
597: }
598:
599: /**
600: * @param text
601: * @return
602: */
603: public boolean containsBookmark(String bookmarkName) {
604:
605: if (bookmarkName == null || bookmarkName.trim().length() == 0) {
606: return false;
607: }
608:
609: synchronized (children) {
610: for (int i = 0; i < children.size(); i++) {
611: Object o = children.get(i);
612: if (o instanceof BookmarkReference) {
613: if (bookmarkName.equals(((BookmarkReference) o)
614: .getName())) {
615: return true;
616: }
617: }
618: }
619: return false;
620: }
621: }
622: }
|