/*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.j2me.pim;
import com.sun.j2me.app.AppPackage;
import com.sun.j2me.security.Permission;
import com.sun.j2me.security.PIMPermission;
import com.sun.j2me.pim.formats.VCalendar10Format;
import com.sun.j2me.pim.formats.VCard21Format;
import com.sun.j2me.pim.formats.VCard30Format;
import com.sun.j2me.i18n.Resource;
import com.sun.j2me.i18n.ResourceConstants;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMException;
import javax.microedition.pim.PIMItem;
import javax.microedition.pim.PIMList;
/**
* Implementation of PIM.
*
*/
public final class PIMImpl extends PIM {
/**
* Supported serial formats.
*/
private static final PIMFormat[] formats = {
new VCalendar10Format(),
new VCard21Format(),
new VCard30Format()
};
/** PIMImpl constructor, called from PIM.getInstance(). */
public PIMImpl() {
}
/**
* Gets an array of PIM items from an encoded input
* stream.
* @param is data input stream
* @param enc character encoding of stream data
* @return array of PIM items
* @throws PIMException if any error reading the PIM data
* @throws UnsupportedEncodingException if encoding is not supported
*/
public PIMItem[] fromSerialFormat(InputStream is, String enc)
throws PIMException, UnsupportedEncodingException {
return fromSerialFormat(is, enc, null);
}
/**
* Gets an array of PIM items from an encoded input
* stream and list.
* @param is data input stream
* @param enc character encoding of stream data
* @param list populate from list
* @return array of PIM items
* @throws PIMException if any error reading the PIM data
* @throws UnsupportedEncodingException if encoding is not supported
*/
private PIMItem[] fromSerialFormat(InputStream is, String enc, PIMList list)
throws PIMException, UnsupportedEncodingException {
if (enc == null) {
enc = "UTF-8" /* NO I18N */;
}
InputStream in = new MarkableInputStream(is);
in.mark(Integer.MAX_VALUE);
try {
for (int i = 0; i < formats.length; i++) {
try {
PIMItem[] items = formats[i].decode(in, enc, list);
if (items == null) {
throw new PIMException(
"Empty stream or insufficient data");
}
return items;
} catch (UnsupportedPIMFormatException e) {
in.reset();
in.mark(Integer.MAX_VALUE);
}
}
throw new PIMException("Format is not recognized");
} catch (UnsupportedEncodingException e) {
throw e;
} catch (IOException e) {
throw new PIMException(e.getMessage());
}
}
/**
* Gets the current PIM lists.
* @param pimListType type of list to return
* @return array of list names
*/
public String[] listPIMLists(int pimListType) {
checkPermissions(pimListType, PIM.READ_ONLY,
Resource.getString(ResourceConstants.JSR75_ENUM_LISTS));
validatePimListType(pimListType);
return PIMHandler.getInstance().getListNames(pimListType);
}
/**
* Gets the permission with detailed description.
*
* @param base one of the base permissions for contact, event, or to-do
* lists
* @param action description of the operation being performed
* @return the detailed permission
*/
private PIMPermission getPermission(PIMPermission base, String action) {
return new PIMPermission(base.getName(), base.getResource() + " (" +
action + ").");
}
/**
* Gets the permissions that need to be present to open a list
*
* @param listType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
* @return list of permissions to be checked
* @throws IllegalArgumentException if one of the parameters
* is out of bounds
*/
private PIMPermission[] getPermissions(int listType, int mode,
String action) {
switch (listType) {
case CONTACT_LIST:
switch (mode) {
case READ_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.CONTACT_READ, action)
};
case WRITE_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.CONTACT_WRITE, action)
};
case READ_WRITE:
return new PIMPermission[] {
getPermission(PIMPermission.CONTACT_READ, action),
getPermission(PIMPermission.CONTACT_WRITE, action)
};
default:
throw new IllegalArgumentException("Not a valid mode: "
+ mode);
}
case EVENT_LIST:
switch (mode) {
case READ_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.EVENT_READ, action)
};
case WRITE_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.EVENT_WRITE, action)
};
case READ_WRITE:
return new PIMPermission[] {
getPermission(PIMPermission.EVENT_READ, action),
getPermission(PIMPermission.EVENT_WRITE, action)
};
default:
throw new IllegalArgumentException("Not a valid mode: "
+ mode);
}
case TODO_LIST:
switch (mode) {
case READ_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.TODO_READ, action)
};
case WRITE_ONLY:
return new PIMPermission[] {
getPermission(PIMPermission.TODO_WRITE, action)
};
case READ_WRITE:
return new PIMPermission[] {
getPermission(PIMPermission.TODO_READ, action),
getPermission(PIMPermission.TODO_WRITE, action)
};
default:
throw new IllegalArgumentException("Not a valid mode: "
+ mode);
}
default:
throw new IllegalArgumentException("Not a valid list type: "
+ listType);
}
}
/**
* Checks for all the permissions that need to be present to open a list
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
* @throws IllegalArgumentException if one of the parameters is out of
* bounds
* @throws SecurityException if the application does not have the required
* permissions
*/
private void checkPermissions(int pimListType, int mode, String action) {
PIMPermission[] permissions = getPermissions(pimListType, mode, action);
AppPackage appPackage = AppPackage.getInstance();
/*
* Do a first pass on the permissions to make sure that none is
* automatically denied. This is for the case when both read permission
* and write permission are required. It is possible that, for example,
* read permission is granted only after asking the user, but write
* permission is automatically denied. In this case, the user should
* not be asked a question at all.
*/
for (int i = 0; i < permissions.length; i++) {
int status = appPackage.checkPermission(permissions[i]);
if (status == 0) {
// throw an exception
appPackage.checkIfPermissionAllowed(permissions[i]);
} else if (status == 1) {
// don't check this permission again
permissions[i] = null;
}
}
for (int i = 0; i < permissions.length; i++) {
if (permissions[i] != null) {
try {
appPackage.checkForPermission(permissions[i]);
} catch (InterruptedException e) {
throw new SecurityException("Security check interrupted: "
+ e.getMessage());
}
}
}
}
/**
* Opens the PIM list.
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
* @return handle to opened PIM list
* @throws PIMException if the list is not found
*/
public PIMList openPIMList(int pimListType, int mode) throws PIMException {
validatePimListType(pimListType);
validateMode(mode);
checkPermissions(pimListType, mode,
Resource.getString(ResourceConstants.JSR75_OPEN_DEFAULT_LIST));
String listName = PIMHandler.getInstance()
.getDefaultListName(pimListType);
if (listName == null) {
throw new PIMException("List not available");
}
return openPIMListImpl(pimListType, mode, listName);
}
/**
* Opens the PIM list.
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
* @param name name of the list
* @return handle to opened PIM list
* @throws PIMException if the list is not found
*/
public PIMList openPIMList(int pimListType, int mode, String name)
throws PIMException {
if (name == null) {
throw new NullPointerException("PIM list name cannot be null");
}
validatePimListType(pimListType);
validateMode(mode);
checkPermissions(pimListType, mode, Resource.getString(
ResourceConstants.JSR75_OPEN_LIST) + ": '" + name + "'");
validateName(pimListType, name);
return openPIMListImpl(pimListType, mode, name);
}
/**
* Does the same as openPIMList, without any validation
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
* @param name name of the list
* @return handle to opened PIM list
* @throws PIMException if the list is not found
*/
private PIMList openPIMListImpl(int pimListType, int mode, String name)
throws PIMException {
AbstractPIMList list;
PIMHandler handler = PIMHandler.getInstance();
Object listHandle = handler.openList(pimListType, name, mode);
switch (pimListType) {
case PIM.CONTACT_LIST:
list = new ContactListImpl(name, mode, listHandle);
break;
case PIM.EVENT_LIST:
list = new EventListImpl(name, mode, listHandle);
break;
case PIM.TODO_LIST:
list = new ToDoListImpl(name, mode, listHandle);
break;
default:
// pimListType has been verified
throw new Error("Unreachable code");
}
Object[] keys = handler.getListKeys(listHandle);
for (int i = 0; i < keys.length; i++) {
byte[] data = handler.getListElement(listHandle, keys[i]);
String [] categories = handler.getListElementCategories(listHandle,
keys[i]);
try {
PIMItem[] items =
fromSerialFormat(new ByteArrayInputStream(data),
"UTF-8", list);
for (int j = 0; j < items.length; j++) {
AbstractPIMItem item = (AbstractPIMItem) items[j];
item.setKey(keys[i]);
list.addItem(item);
item.setDefaultValues();
for (int index = 0; index < categories.length; index ++) {
item.addToCategory(categories[index]);
}
item.setModified(false);
}
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported");
} catch (PIMException e) {
// skip element
}
}
return list;
}
/**
* Gets the list of supported serial formats.
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @return array of format names
*/
public String[] supportedSerialFormats(int pimListType) {
validatePimListType(pimListType);
int supportedFormatCount = 0;
for (int i = 0; i < formats.length; i++) {
if (formats[i].isTypeSupported(pimListType)) {
supportedFormatCount ++;
}
}
String[] supportedFormats = new String[supportedFormatCount];
for (int i = 0; i < formats.length; i++) {
if (formats[i].isTypeSupported(pimListType)) {
supportedFormats[--supportedFormatCount] = formats[i].getName();
}
}
return supportedFormats;
}
/**
* Converts to serial format.
* @param item the PIM item to be processed
* @param os the target output stream
* @param enc the character encoding for output strings
* @param dataFormat the serialized format
* @throws PIMException if any error writing the PIM data
* @throws UnsupportedEncodingException if encoding is not supported
*/
public void toSerialFormat(PIMItem item,
OutputStream os,
String enc,
String dataFormat) throws PIMException, UnsupportedEncodingException {
if (enc == null) {
enc = "UTF-8" /* NO I18N */;
}
if (dataFormat == null) {
throw new NullPointerException("Null data format");
}
if (dataFormat.trim().length() == 0) {
throw new IllegalArgumentException("Empty data format");
}
if (item == null) {
throw new NullPointerException("Null PIM item");
}
try {
for (int i = 0; i < formats.length; i++) {
if (formats[i].getName().equals(dataFormat)) {
formats[i].encode(os, enc, item);
return;
}
}
throw new IllegalArgumentException("Data format '" + dataFormat
+ "' is not supported");
} catch (UnsupportedEncodingException e) {
throw e;
} catch (IOException e) {
throw new PIMException(e.getMessage());
}
}
/**
* Ensures that the given PIM list type is valid.
* @param pimListType a PIM list type
* @throws IllegalArgumentException if the list type is not valid.
*/
private void validatePimListType(int pimListType) {
switch (pimListType) {
case PIM.CONTACT_LIST:
case PIM.EVENT_LIST:
case PIM.TODO_LIST:
// ok
break;
default:
throw new IllegalArgumentException("Not a valid PIM list type: "
+ pimListType);
}
}
/**
* Ensures that the given PIM list mode is valid.
* @param mode READ_ONLY, WRITE_ONLY or READ_WRITE
*/
private void validateMode(int mode) {
switch (mode) {
case READ_ONLY:
case WRITE_ONLY:
case READ_WRITE:
break;
default:
throw new IllegalArgumentException(
"Invalid PIM list mode: " + mode);
}
}
/**
* Ensures that the given PIM list name is valid.
*
* @param pimListType CONTACT_LIST, EVENT_LIST or TODO_LIST
* @param name name of the list
* @throws PIMException if list with the specified name is not found
*/
private void validateName(int pimListType, String name)
throws PIMException {
String[] names = PIMHandler.getInstance().getListNames(pimListType);
for (int i = 0; i < names.length; i++) {
if (name.equals(names[i])) {
return;
}
}
throw new PIMException("PIM list does not exist: '" + name + "'");
}
}