001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.kvem.midp.pim;
028:
029: import java.util.Enumeration;
030: import java.util.NoSuchElementException;
031: import java.util.Vector;
032: import javax.microedition.pim.PIM;
033: import javax.microedition.pim.PIMException;
034: import javax.microedition.pim.PIMItem;
035: import javax.microedition.pim.PIMList;
036: import javax.microedition.pim.UnsupportedFieldException;
037:
038: /**
039: * Generic PIM list.
040: *
041: */
042: public abstract class AbstractPIMList implements PIMList {
043: /** Label for the list. */
044: private final String name;
045:
046: /** Items in the list. */
047: private Vector items = new Vector();
048: /** Read or write mode allowed for the list. */
049: private int mode;
050: /** Current state of the list. */
051: private boolean open = true;
052: /** The type of the list. */
053: private final int type;
054: /** List handle */
055: private Object handle;
056:
057: /**
058: * Base type for PIM list structures.
059: * @param type identifier for list type
060: * @param name label for the list
061: * @param mode readable or writable mode
062: * @param handle handle of the list
063: */
064: AbstractPIMList(int type, String name, int mode, Object handle) {
065: this .type = type;
066: this .name = name;
067: this .mode = mode;
068: this .handle = handle;
069: }
070:
071: // JAVADOC COMMENT ELIDED
072: public void addCategory(String category) throws PIMException {
073: checkWritePermission();
074: checkOpen();
075: checkNullCategory(category);
076: PIMHandler handler = PIMHandler.getInstance();
077: String[] categories = handler.getCategories(handle);
078: for (int i = 0; i < categories.length; i++) {
079: if (category.equals(categories[i])) {
080: return;
081: }
082: }
083: handler.addCategory(handle, category);
084: String[] newCategories = new String[categories.length + 1];
085: System.arraycopy(categories, 0, newCategories, 0,
086: categories.length);
087: newCategories[categories.length] = category;
088: }
089:
090: // JAVADOC COMMENT ELIDED
091: public Enumeration itemsByCategory(String category)
092: throws PIMException {
093: checkReadPermission();
094: checkOpen();
095: Vector v = new Vector();
096: if (category == null || category.equals("UNCATEGORIZED")) {
097: for (Enumeration e = items.elements(); e.hasMoreElements();) {
098: AbstractPIMItem item = (AbstractPIMItem) e
099: .nextElement();
100: if (item.getCategoriesRaw() == null) {
101: v.addElement(item);
102: }
103: }
104: } else {
105: for (Enumeration e = items.elements(); e.hasMoreElements();) {
106: AbstractPIMItem item = (AbstractPIMItem) e
107: .nextElement();
108: if (item.isInCategory(category)) {
109: v.addElement(item);
110: }
111: }
112: }
113: return v.elements();
114: }
115:
116: // JAVADOC COMMENT ELIDED
117: public Enumeration items(PIMItem matchingItem) throws PIMException {
118: checkReadPermission();
119: checkOpen();
120: if (!equals(matchingItem.getPIMList())) {
121: throw new IllegalArgumentException("Cannot match item "
122: + "from another list");
123: }
124: int[] searchFields = matchingItem.getFields();
125: int[] searchFieldDataTypes = new int[searchFields.length];
126: Object[][] searchData = new Object[searchFields.length][];
127: for (int i = 0; i < searchFields.length; i++) {
128: searchFieldDataTypes[i] = getFieldDataType(searchFields[i]);
129: searchData[i] = new Object[matchingItem
130: .countValues(searchFields[i])];
131: for (int j = 0; j < searchData[i].length; j++) {
132: searchData[i][j] = ((AbstractPIMItem) matchingItem)
133: .getField(searchFields[i], false, false)
134: .getValue(j);
135: // convert strings to upper case so that they can be matched
136: // case-insensitively
137: switch (searchFieldDataTypes[i]) {
138: case PIMItem.STRING: {
139: String s = (String) searchData[i][j];
140: if (s != null) {
141: searchData[i][j] = s.toUpperCase();
142: }
143: break;
144: }
145: case PIMItem.STRING_ARRAY: {
146: String[] a = (String[]) searchData[i][j];
147: if (a != null) {
148: for (int k = 0; k < a.length; k++) {
149: if (a[k] != null) {
150: a[k] = a[k].toUpperCase();
151: }
152: }
153: }
154: break;
155: }
156: }
157: }
158: }
159: Vector v = new Vector();
160: nextItem: for (Enumeration e = items.elements(); e
161: .hasMoreElements();) {
162: AbstractPIMItem item = (AbstractPIMItem) e.nextElement();
163: // For each field, make sure all data has a match in "item".
164: for (int i = 0; i < searchFields.length; i++) {
165: // make sure that every value searchData[i][j]
166: // (0 < j < matchingItem.countValues())
167: // has a match in "item".
168: int field = searchFields[i];
169: int itemIndices = item.countValues(field);
170: for (int j = 0; j < searchData[i].length; j++) {
171: boolean matchedThisIndex = false;
172: // see if there is a match for searchData[i][j]
173: for (int k = 0; k < itemIndices
174: && !matchedThisIndex; k++) {
175: Object value = item.getField(field, false,
176: false).getValue(k);
177: // see if "value" matches searchData[i][j]
178: switch (searchFieldDataTypes[i]) {
179: case PIMItem.DATE:
180: case PIMItem.INT:
181: case PIMItem.BOOLEAN: {
182: if (searchData[i][j].equals(value)) {
183: matchedThisIndex = true;
184: }
185: break;
186: }
187: case PIMItem.BINARY: {
188: byte[] a = (byte[]) searchData[i][j];
189: byte[] b = (byte[]) value;
190: if (b != null && a.length == b.length) {
191: boolean arrayMatches = true;
192: for (int m = 0; m < a.length
193: && arrayMatches; m++) {
194: arrayMatches = (a[m] == b[m]);
195: }
196: matchedThisIndex = arrayMatches;
197: }
198: break;
199: }
200: case PIMItem.STRING: {
201: // strings are matched case-insensitively
202: // and using contains(), not equals()
203: String s1 = (String) searchData[i][j];
204: String s2 = (String) value;
205: if (s2 == null) {
206: if (s1 == null) {
207: matchedThisIndex = true;
208: }
209: } else if (s2.toUpperCase().indexOf(s1) != -1) {
210: matchedThisIndex = true;
211: }
212: break;
213: }
214: case PIMItem.STRING_ARRAY: {
215: // each element of the string array is matched
216: // in the same way as a string (above) would be.
217: String[] a = (String[]) searchData[i][j];
218: String[] b = (String[]) value;
219: if (a == null) {
220: if (b == null) {
221: matchedThisIndex = true;
222: }
223: } else if (b != null
224: && a.length == b.length) {
225: boolean arrayMatches = true;
226: for (int m = 0; m < a.length
227: && arrayMatches; m++) {
228: if (a[m] != null
229: && a[m].length() > 0) {
230: arrayMatches = (b[m] != null)
231: && b[m].toUpperCase()
232: .indexOf(a[m]) != -1;
233: }
234: }
235: matchedThisIndex = arrayMatches;
236: }
237: break;
238: }
239: } // end switch data type
240: } // end loop over item's indices
241: if (!matchedThisIndex) {
242: // no match found for searchData[i][j].
243: // this means that "item" does not match "matchingItem"
244: continue nextItem;
245: }
246: } // end iteration over matchingItem's indices
247: } // end iteration over fields being searched
248: v.addElement(item);
249: } // end enumeration of items
250: return v.elements();
251: }
252:
253: // JAVADOC COMMENT ELIDED
254: public void close() throws PIMException {
255: checkOpen();
256: this .open = false;
257: this .items = null;
258: PIMHandler.getInstance().closeList(handle);
259: }
260:
261: // JAVADOC COMMENT ELIDED
262: public String getArrayElementLabel(int stringArrayField,
263: int arrayElement) {
264: if (getFieldDataType(stringArrayField) != PIMItem.STRING_ARRAY) {
265: throw new IllegalArgumentException(
266: "Not a string array field");
267: }
268: if (!isSupportedArrayElement(stringArrayField, arrayElement)) {
269: // throw new UnsupportedFieldException("Unsupported array element "
270: // + arrayElement);
271: throw new IllegalArgumentException("Invalid array element "
272: + arrayElement);
273: }
274: return PIMHandler.getInstance().getArrayElementLabel(handle,
275: stringArrayField, arrayElement);
276: }
277:
278: // JAVADOC COMMENT ELIDED
279: public int[] getSupportedArrayElements(int stringArrayField) {
280: if (getFieldDataType(stringArrayField) != PIMItem.STRING_ARRAY) {
281: throw new IllegalArgumentException(
282: "Not a string array field");
283: }
284: return PIMHandler.getInstance().getSupportedArrayElements(
285: handle, stringArrayField);
286: }
287:
288: // JAVADOC COMMENT ELIDED
289: public String getAttributeLabel(int attribute) {
290: checkAttribute(attribute);
291: String label = PIMHandler.getInstance().getAttributeLabel(
292: handle, attribute);
293: if (label == null) {
294: throw new IllegalArgumentException("Invalid attribute: "
295: + attribute);
296: }
297: return label;
298: }
299:
300: // JAVADOC COMMENT ELIDED
301: public int maxValues(int field) {
302: try {
303: checkField(field);
304: } catch (UnsupportedFieldException e) {
305: return 0;
306: }
307: return PIMHandler.getInstance().getMaximumValues(handle, field);
308: }
309:
310: // JAVADOC COMMENT ELIDED
311: public boolean isSupportedAttribute(int field, int attribute) {
312: if (!isSupportedField(field)) {
313: return false;
314: }
315: // ATTR_NONE is supported for all fields
316: if (attribute == PIMItem.ATTR_NONE) {
317: return true;
318: }
319: // if attribute is not a power of 2, forget it
320: int i = attribute;
321: while ((i & 1) == 0 && i != 0) {
322: i >>= 1;
323: }
324: if (i != 1) {
325: return false;
326: }
327: return PIMHandler.getInstance().isSupportedAttribute(handle,
328: field, attribute);
329: }
330:
331: // JAVADOC COMMENT ELIDED
332: public int maxCategories() {
333: return -1;
334: }
335:
336: // JAVADOC COMMENT ELIDED
337: public String[] getCategories() throws PIMException {
338: checkOpen();
339: return PIMHandler.getInstance().getCategories(handle);
340: }
341:
342: // JAVADOC COMMENT ELIDED
343: public boolean isCategory(String category) throws PIMException {
344: checkOpen();
345: checkNullCategory(category);
346: String[] categories = PIMHandler.getInstance().getCategories(
347: handle);
348: for (int i = 0; i < categories.length; i++) {
349: if (category.equals(categories[i])) {
350: return true;
351: }
352: }
353: return false;
354: }
355:
356: // JAVADOC COMMENT ELIDED
357: public String getName() {
358: return name;
359: }
360:
361: // JAVADOC COMMENT ELIDED
362: public int stringArraySize(int stringArrayField) {
363: if (getFieldDataType(stringArrayField) != PIMItem.STRING_ARRAY) {
364: throw new IllegalArgumentException(
365: "Not a string array field");
366: }
367: return PIMHandler.getInstance().getStringArraySize(handle,
368: stringArrayField);
369: }
370:
371: // JAVADOC COMMENT ELIDED
372: public boolean isSupportedField(int field) {
373: return PIMHandler.getInstance().isSupportedField(handle, field);
374: }
375:
376: // JAVADOC COMMENT ELIDED
377: public void deleteCategory(String category,
378: boolean deleteUnassignedItems) throws PIMException {
379: checkWritePermission();
380: checkOpen();
381: checkNullCategory(category);
382: String[] categories = PIMHandler.getInstance().getCategories(
383: handle);
384: int categoryIndex = -1;
385: for (int i = 0; i < categories.length && categoryIndex == -1; i++) {
386: if (category.equals(categories[i])) {
387: categoryIndex = i;
388: }
389: }
390: if (categoryIndex == -1) {
391: return;
392: }
393: String[] newCategories = new String[categories.length - 1];
394: System
395: .arraycopy(categories, 0, newCategories, 0,
396: categoryIndex);
397: System.arraycopy(categories, categoryIndex + 1, newCategories,
398: categoryIndex, newCategories.length - categoryIndex);
399: PIMHandler.getInstance().deleteCategory(handle, category);
400: final AbstractPIMItem[] a = new AbstractPIMItem[items.size()];
401: items.copyInto(a);
402: for (int i = 0; i < a.length; i++) {
403: if (a[i].isInCategory(category)) {
404: a[i].removeFromCategory(category);
405: if (deleteUnassignedItems
406: && a[i].getCategories().length == 0) {
407: items.removeElement(a[i]);
408: }
409: }
410: }
411: }
412:
413: // JAVADOC COMMENT ELIDED
414: public Enumeration items() throws PIMException {
415: checkReadPermission();
416: checkOpen();
417: final PIMItem[] data = new PIMItem[items.size()];
418: items.copyInto(data);
419: return new
420: /**
421: * Inner class for List Enumeration.
422: */
423: Enumeration() {
424: /** Initial offset int the list. */
425: int index = 0;
426:
427: /**
428: * Checks for more elements int the list.
429: * @return <code>true</code> if more elements exist
430: */
431: public boolean hasMoreElements() {
432: return index < data.length;
433: }
434:
435: /**
436: * Fetched the next element int the list.
437: * @return next element int the list
438: */
439: public Object nextElement() {
440: try {
441: return data[index++];
442: } catch (ArrayIndexOutOfBoundsException e) {
443: throw new NoSuchElementException();
444: }
445: }
446: };
447: }
448:
449: // JAVADOC COMMENT ELIDED
450: public int[] getSupportedAttributes(int field) {
451: checkField(field);
452: return PIMHandler.getInstance().getSupportedAttributes(handle,
453: field);
454: }
455:
456: // JAVADOC COMMENT ELIDED
457: public int getFieldDataType(int field) {
458: int dataType = PIMHandler.getInstance().getFieldDataType(
459: handle, field);
460: if (dataType == -1) {
461: throw AbstractPIMItem.complaintAboutField(type, field);
462: }
463: return dataType;
464: }
465:
466: // JAVADOC COMMENT ELIDED
467: public String getFieldLabel(int field) {
468: checkField(field);
469: return PIMHandler.getInstance().getFieldLabel(handle, field);
470: }
471:
472: // JAVADOC COMMENT ELIDED
473: public void renameCategory(String currentCategory,
474: String newCategory) throws PIMException {
475: checkWritePermission();
476: checkOpen();
477: if (currentCategory == null || newCategory == null) {
478: throw new NullPointerException("Null category");
479: }
480: String[] categories = PIMHandler.getInstance().getCategories(
481: handle);
482: if (newCategory.equals(currentCategory)) {
483: return;
484: }
485: int oldCategoryIndex = -1;
486: int newCategoryIndex = -1;
487: for (int i = 0; i < categories.length; i++) {
488: if (currentCategory.equals(categories[i])) {
489: oldCategoryIndex = i;
490: } else if (newCategory.equals(categories[i])) {
491: newCategoryIndex = i;
492: }
493: }
494: if (oldCategoryIndex == -1) {
495: throw new PIMException("No such category: "
496: + currentCategory);
497: }
498: if (newCategoryIndex == -1) {
499: categories[oldCategoryIndex] = newCategory;
500: PIMHandler.getInstance().renameCategory(handle,
501: currentCategory, newCategory);
502: } else {
503: String[] a = new String[categories.length - 1];
504: System.arraycopy(categories, 0, a, 0, oldCategoryIndex);
505: System.arraycopy(categories, oldCategoryIndex + 1, a,
506: oldCategoryIndex, a.length - oldCategoryIndex);
507: categories = a;
508: PIMHandler.getInstance().deleteCategory(handle,
509: currentCategory);
510: }
511: for (Enumeration e = items.elements(); e.hasMoreElements();) {
512: AbstractPIMItem item = (AbstractPIMItem) e.nextElement();
513: if (item.isInCategory(currentCategory)) {
514: item.removeFromCategory(currentCategory);
515: item.addToCategory(newCategory);
516: }
517: }
518: }
519:
520: // JAVADOC COMMENT ELIDED
521: public boolean isSupportedArrayElement(int stringArrayField,
522: int arrayElement) {
523: int dataType = PIMHandler.getInstance().getFieldDataType(
524: handle, stringArrayField);
525: if (dataType != PIMItem.STRING_ARRAY) {
526: return false;
527: }
528: return PIMHandler.getInstance().isSupportedArrayElement(handle,
529: stringArrayField, arrayElement);
530: }
531:
532: // JAVADOC COMMENT ELIDED
533: public int[] getSupportedFields() {
534: return PIMHandler.getInstance().getSupportedFields(handle);
535: }
536:
537: // JAVADOC COMMENT ELIDED
538: public Enumeration items(String matchingValue) throws PIMException {
539: checkReadPermission();
540: checkOpen();
541: matchingValue = matchingValue.toUpperCase();
542: Vector v = new Vector();
543: nextItem: for (Enumeration e = items.elements(); e
544: .hasMoreElements();) {
545: AbstractPIMItem item = (AbstractPIMItem) e.nextElement();
546: int[] fields = item.getFields();
547: for (int i = 0; i < fields.length; i++) {
548: switch (getFieldDataType(fields[i])) {
549: case PIMItem.STRING:
550: for (int j = item.countValues(fields[i]) - 1; j >= 0; j--) {
551: String value = item.getString(fields[i], j);
552: if (value != null) {
553: if (value.toUpperCase().indexOf(
554: matchingValue) != -1) {
555: v.addElement(item);
556: continue nextItem;
557: }
558: }
559: }
560: break;
561: case PIMItem.STRING_ARRAY:
562: for (int j = item.countValues(fields[i]) - 1; j >= 0; j--) {
563: String[] a = item.getStringArray(fields[i], j);
564: if (a == null) {
565: continue;
566: }
567: for (int k = 0; k < a.length; k++) {
568: if (a[k] != null) {
569: if (a[k].toUpperCase().indexOf(
570: matchingValue) != -1) {
571: v.addElement(item);
572: continue nextItem;
573: }
574: }
575: }
576: }
577: break;
578: }
579: }
580: }
581: return v.elements();
582: }
583:
584: /**
585: * Verifies read permission.
586: * @throws SecurityException if operation is not permitted
587: */
588: protected void checkReadPermission() throws SecurityException {
589: if (mode == PIM.WRITE_ONLY) {
590: throw new SecurityException("List cannot be read");
591: }
592: }
593:
594: /**
595: * Verifies write permission.
596: * @throws SecurityException if operation is not permitted
597: */
598: protected void checkWritePermission() throws SecurityException {
599: if (mode == PIM.READ_ONLY) {
600: throw new SecurityException("List cannot be written");
601: }
602: }
603:
604: /**
605: * Ensures that the list is already open.
606: * @throws PIMException if list is closed
607: */
608: protected void checkOpen() throws PIMException {
609: if (!open) {
610: throw new PIMException("List is closed.");
611: }
612: }
613:
614: /**
615: * Ensure the category is not missing.
616: * @throws NullPointerException if category is
617: * <code>null</code>
618: * @param category data to validate
619: */
620: protected void checkNullCategory(String category) {
621: if (category == null) {
622: throw new NullPointerException("Null category");
623: }
624: }
625:
626: /**
627: * Adds an item to a PIM list.
628: * @param item item to add to the list
629: */
630: public void addItem(AbstractPIMItem item) {
631: this .items.addElement(item);
632: item.setPIMList(this );
633: }
634:
635: /**
636: * Removes an item from a PIM list.
637: * @param item to add to the list
638: * @throws PIMException if the item was not int the list
639: * @throws SecurityException if operation is not permitted
640: * @throws NullPointerException if item is <code>null</code>
641: */
642: void removeItem(PIMItem item) throws PIMException {
643: checkWritePermission();
644: checkOpen();
645: if (item == null) {
646: throw new NullPointerException("Null item");
647: }
648: if (!this .items.removeElement(item)) {
649: throw new PIMException("Item not in list");
650: }
651: ((AbstractPIMItem) item).remove();
652: }
653:
654: /**
655: * Commits an item to the database
656: *
657: * @param key the key of the item to commit. This can be null.
658: * @param data the binary data for the item
659: * @param categories list of item's categories
660: * @return an updated non-null key for the item
661: * @throws PIMException in case of I/O error
662: */
663: Object commit(Object key, byte[] data, String[] categories)
664: throws PIMException {
665: return PIMHandler.getInstance().commitListElement(handle, key,
666: data, categories);
667: }
668:
669: /**
670: * Gets the type of this PIM list (PIM.CONTACT_LIST, PIM.EVENT_LIST or
671: * PIM.TODO_LIST).
672: * @return the type of the PIM list
673: */
674: int getType() {
675: return type;
676: }
677:
678: /**
679: * Returns list handle
680: *
681: * @return handle of the list
682: */
683: public Object getHandle() {
684: return handle;
685: }
686:
687: /**
688: * Ensures the field is valid.
689: * @throws IllegalArgumentException if field is not a valid
690: * field (i.e. not a standard field and not an extended field).
691: * IllegalArgumentException takes precedence over
692: * UnsupportedFieldException when checking the provided field.
693: * @throws UnsupportedFieldException if the field is not supported in
694: * the implementing instance of the class.
695: * @param field Field to validate
696: */
697: private void checkField(int field) {
698: if (PIMHandler.getInstance().getFieldDataType(handle, field) == -1) {
699: throw AbstractPIMItem.complaintAboutField(type, field);
700: }
701: }
702:
703: /**
704: * Ensures the attribute is valid.
705: * @throws IllegalArgumentException more than one bit set
706: * in attribute selector
707: * @param attribute identifier for specific attribute
708: */
709: private void checkAttribute(int attribute) {
710: if (attribute == 0) {
711: // OK
712: return;
713: } else {
714: // make sure only one bit is set
715: while ((attribute & 1) == 0 && attribute != 0) {
716: attribute >>= 1;
717: }
718: if (attribute != 1) {
719: // more than one bit is set in attribute
720: throw new IllegalArgumentException(
721: "Invalid attribute: " + attribute);
722: }
723: }
724: }
725:
726: }
|