0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010:
0011: package org.mmbase.util;
0012:
0013: /**
0014: * Collects MMBase specific 'cast' information, as static to... functions. This is used (and used to
0015: * be implemented) in MMObjectNode. But this functionality is more generic to MMbase.
0016: *
0017: * @author Michiel Meeuwissen
0018: * @since MMBase-1.6
0019: * @version $Id: Casting.java,v 1.108 2007/12/11 14:47:19 michiel Exp $
0020: */
0021:
0022: import java.util.*;
0023: import java.text.*;
0024: import java.io.*;
0025: import javax.xml.parsers.*;
0026: import org.mmbase.bridge.*;
0027: import org.mmbase.bridge.Node;
0028: import org.mmbase.bridge.util.NodeWrapper;
0029: import org.mmbase.bridge.util.NodeMap;
0030: import org.mmbase.bridge.util.MapNode;
0031: import org.mmbase.util.transformers.CharTransformer;
0032: import org.mmbase.util.logging.*;
0033: import org.mmbase.util.xml.XMLWriter;
0034:
0035: import org.w3c.dom.*;
0036:
0037: public class Casting {
0038:
0039: private static final Logger log = Logging
0040: .getLoggerInstance(Casting.class);
0041:
0042: /**
0043: * A Date formatter that creates a date based on a ISO 8601 date and a ISO 8601 time.
0044: * I.e. 2004-12-01 14:30:00.
0045: * It is NOT 100% ISO 8601, as opposed to {@link #ISO_8601_UTC}, as the standard actually requires
0046: * a 'T' to be placed between the date and the time.
0047: * The date given is the date for the local (server) time. Use this formatter if you want to display
0048: * user-friendly dates in local time.
0049:
0050: * XXX: According to http://en.wikipedia.org/wiki/ISO_8601, the standard allows ' ' in stead of
0051: * 'T' if no misunderstanding arises, which is the case here. So I don't think this is 'loose'.
0052: */
0053: public final static ThreadLocal<DateFormat> ISO_8601_LOOSE = new ThreadLocal<DateFormat>() {
0054: protected synchronized DateFormat initialValue() {
0055: return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
0056: Locale.US);
0057: }
0058: };
0059:
0060: /**
0061: * A Date formatter that creates a ISO 8601 datetime according to UTC/GMT.
0062: * I.e. 2004-12-01T14:30:00Z.
0063: * This is 100% ISO 8601, as opposed to {@link #ISO_8601_LOOSE}.
0064: * Use this formatter if you want to export dates.
0065: *
0066: * XXX: Hmm, we parse with UTC now, while we don't store them as such.
0067: */
0068: public final static ThreadLocal<DateFormat> ISO_8601_UTC = new ThreadLocal<DateFormat>() {
0069: protected synchronized DateFormat initialValue() {
0070: DateFormat df = new SimpleDateFormat(
0071: "yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
0072: try {
0073: df.setTimeZone(TimeZone.getTimeZone("UTC"));
0074: } catch (Throwable t) {
0075: log.warn(t.getMessage(), t);
0076: }
0077: return df;
0078: }
0079: };
0080:
0081: public final static ThreadLocal<DateFormat> ISO_8601_DATE = new ThreadLocal<DateFormat>() {
0082: protected synchronized DateFormat initialValue() {
0083: return new SimpleDateFormat("yyyy-MM-dd", Locale.US);
0084: }
0085: };
0086: public final static ThreadLocal<DateFormat> ISO_8601_TIME = new ThreadLocal<DateFormat>() {
0087: protected synchronized DateFormat initialValue() {
0088: return new SimpleDateFormat("HH:mm:ss", Locale.US);
0089: }
0090: };
0091:
0092: /**
0093: * Returns whether the passed object is of the given class.
0094: * Unlike Class instanceof this also includes Object Types that
0095: * are representative for primitive types (i.e. Integer for int).
0096: * @param type the type (class) to check
0097: * @param value the value whose type to check
0098: * @return <code>true</code> if compatible
0099: * @since MMBase-1.8
0100: */
0101: public static boolean isType(Class type, Object value) {
0102: if (type.isPrimitive()) {
0103: return (type.equals(Boolean.TYPE) && value instanceof Boolean)
0104: || (type.equals(Byte.TYPE) && value instanceof Byte)
0105: || (type.equals(Character.TYPE) && value instanceof Character)
0106: || (type.equals(Short.TYPE) && value instanceof Short)
0107: || (type.equals(Integer.TYPE) && value instanceof Integer)
0108: || (type.equals(Long.TYPE) && value instanceof Long)
0109: || (type.equals(Float.TYPE) && value instanceof Float)
0110: || (type.equals(Double.TYPE) && value instanceof Double);
0111: } else {
0112: return value == null || type.isInstance(value);
0113: }
0114: }
0115:
0116: /**
0117: * Tries to 'cast' an object for use with the provided class. E.g. if value is a String, but the
0118: * type passed is Integer, then the string is act to an Integer.
0119: * If the type passed is a primitive type, the object is cast to an Object Types that is representative
0120: * for that type (i.e. Integer for int).
0121: * @param type the type (class)
0122: * @param value The value to be converted
0123: * @return value the converted value
0124: * @since MMBase-1.8
0125: */
0126: public static <C> C toType(Class<C> type, Object value) {
0127: return toType(type, null, value);
0128: }
0129:
0130: private static Cloud anonymousCloud = null;
0131:
0132: /**
0133: * Tries to 'cast' an object for use with the provided class. E.g. if value is a String, but the
0134: * type passed is Integer, then the string is act to an Integer.
0135: * If the type passed is a primitive type, the object is cast to an Object Types that is representative
0136: * for that type (i.e. Integer for int).
0137: * @param type the type (class)
0138: * @param cloud When casting to Node, a cloud may be needed. May be <code>null</code>, for an anonymous cloud to be tried.
0139: * @param value The value to be converted
0140: * @return value the converted value
0141: * @since MMBase-1.8
0142: */
0143: public static <C> C toType(Class<C> type, Cloud cloud, Object value) {
0144: if (value != null && isType(type, value)) {
0145: return (C) value;
0146: } else {
0147: if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) {
0148: return (C) Boolean.valueOf(toBoolean(value));
0149: } else if (type.equals(Byte.TYPE)
0150: || type.equals(Byte.class)) {
0151: return (C) Byte.valueOf(toInteger(value).byteValue());
0152: } else if (type.equals(Character.TYPE)
0153: || type.equals(Character.class)) {
0154: String chars = toString(value);
0155: if (chars.length() > 0) {
0156: return (C) Character.valueOf(chars.charAt(0));
0157: } else {
0158: return (C) Character.valueOf(Character.MIN_VALUE);
0159: }
0160: } else if (type.equals(Short.TYPE)
0161: || type.equals(Short.class)) {
0162: return (C) Short.valueOf(toInteger(value).shortValue());
0163: } else if (type.equals(Integer.TYPE)
0164: || type.equals(Integer.class)) {
0165: return (C) toInteger(value);
0166: } else if (type.equals(Long.TYPE)
0167: || type.equals(Long.class)) {
0168: return (C) Long.valueOf(toLong(value));
0169: } else if (type.equals(Float.TYPE)
0170: || type.equals(Float.class)) {
0171: return (C) Float.valueOf(toFloat(value));
0172: } else if (type.equals(Double.TYPE)
0173: || type.equals(Double.class)) {
0174: return (C) Double.valueOf(toDouble(value));
0175: } else if (type.equals(Number.class)) {
0176: Number res;
0177: try {
0178: res = Long.valueOf("" + value);
0179: } catch (NumberFormatException nfe) {
0180: try {
0181: res = Double.valueOf("" + value);
0182: } catch (NumberFormatException nfe1) {
0183: res = Integer.valueOf(-1);
0184: }
0185: }
0186: return (C) res;
0187: } else if (type.equals(byte[].class)) {
0188: return (C) toByte(value);
0189: } else if (type.equals(String.class)) {
0190: return (C) toString(value);
0191: } else if (type.equals(Date.class)) {
0192: return (C) toDate(value);
0193: } else if (type.equals(Node.class)) {
0194: try {
0195: if (cloud == null) {
0196: if (anonymousCloud == null)
0197: anonymousCloud = ContextProvider
0198: .getDefaultCloudContext().getCloud(
0199: "mmbase");
0200: cloud = anonymousCloud;
0201: }
0202: return (C) toNode(value, cloud);
0203: } catch (Exception e) {
0204: // suppose that that was because mmbase not running
0205: return (C) (value instanceof Node ? value : null);
0206: }
0207: } else if (type.equals(Document.class)) {
0208: return (C) toXML(value);
0209: } else if (type.equals(List.class)) {
0210: return (C) toList(value);
0211: } else if (type.equals(Map.class)) {
0212: return (C) toMap(value);
0213: } else if (type.equals(Collection.class)) {
0214: return (C) toCollection(value);
0215: } else {
0216: if (value == null || "".equals(value)) {
0217: // just to avoid the error
0218: return null;
0219: }
0220: log.error("Dont now how to convert to " + type);
0221: // don't know
0222: return (C) value;
0223: }
0224: }
0225: }
0226:
0227: /**
0228: * Whether or not Casting can more or less reliably cast a certain type to String and back.
0229: * For collection types also the entries of the collection must be string representable.
0230: * @since MMBase-1.8
0231: */
0232: public static boolean isStringRepresentable(Class type) {
0233: return CharSequence.class.isAssignableFrom(type)
0234: || Number.class.isAssignableFrom(type)
0235: || Boolean.TYPE.isAssignableFrom(type)
0236: || Boolean.class.isAssignableFrom(type)
0237: || Character.class.isAssignableFrom(type)
0238: || Node.class.isAssignableFrom(type)
0239: || Document.class.isAssignableFrom(type)
0240: || Collection.class.isAssignableFrom(type)
0241: || Date.class.isAssignableFrom(type)
0242: || Map.class.isAssignableFrom(type);
0243: }
0244:
0245: /**
0246: * Convert an object to a String.
0247: * <code>null</code> is converted to an empty string.
0248: * @param o the object to convert
0249: * @return the converted value as a <code>String</code>
0250: */
0251: public static String toString(Object o) {
0252: if (o instanceof String) {
0253: return (String) o;
0254: }
0255: if (o == null || "".equals(o)) {
0256: return "";
0257: }
0258:
0259: return toStringBuilder(new StringBuilder(), o).toString();
0260: }
0261:
0262: /**
0263: * Convert an object to a string, using a StringBuffer.
0264: * @param buffer The StringBuffer with which to create the string
0265: * @param o the object to convert
0266: * @return the StringBuffer used for conversion (same as the buffer parameter)
0267: * @since MMBase-1.7
0268: */
0269: public static StringBuffer toStringBuffer(StringBuffer buffer,
0270: Object o) {
0271: if (o == null) {
0272: return buffer;
0273: }
0274: try {
0275: toWriter(new StringBufferWriter(buffer), o);
0276: } catch (java.io.IOException e) {
0277: }
0278: return buffer;
0279: }
0280:
0281: /**
0282: * Convert an object to a string, using a StringBuilder
0283: * @param buffer The StringBuilder with which to create the string
0284: * @param o the object to convert
0285: * @return the StringBuilder used for conversion (same as the buffer parameter)
0286: * @since MMBase-1.9
0287: */
0288: public static StringBuilder toStringBuilder(StringBuilder buffer,
0289: Object o) {
0290: if (o == null) {
0291: return buffer;
0292: }
0293: try {
0294: toWriter(new StringBuilderWriter(buffer), o);
0295: } catch (java.io.IOException e) {
0296: }
0297: return buffer;
0298: }
0299:
0300: private static org.mmbase.storage.search.implementation.database.BasicSqlHandler sqlHandler = new org.mmbase.storage.search.implementation.database.BasicSqlHandler();
0301:
0302: /**
0303: * Convert an object to a string, using a Writer.
0304: * @param writer The Writer with which to create (write) the string
0305: * @param o the object to convert
0306: * @return the Writer used for conversion (same as the writer parameter)
0307: * @since MMBase-1.7
0308: */
0309: public static Writer toWriter(Writer writer, Object o)
0310: throws java.io.IOException {
0311: if (o instanceof Writer) {
0312: return writer;
0313: }
0314: Object s = wrap(o, null);
0315: if (s instanceof org.mmbase.storage.search.SearchQuery) {
0316: try {
0317: s = sqlHandler.toSql(
0318: (org.mmbase.storage.search.SearchQuery) s,
0319: sqlHandler);
0320: } catch (org.mmbase.storage.search.SearchQueryException sqe) {
0321: }
0322: }
0323: writer.write(s.toString());
0324: return writer;
0325: }
0326:
0327: /**
0328: * Wraps an object in another object with a toString as we desire. Casting can now be done with
0329: * toString() on the resulting object.
0330: *
0331: * This is used to make JSTL en EL behave similarly as mmbase taglib when writing objects to the
0332: * page (taglib calls Casting, but JSTL of course doesn't).
0333: *
0334: * @todo Not everything is wrapped (and can be unwrapped) already.
0335: * @param o The object to be wrapped
0336: * @param escaper <code>null</code> or a CharTransformer to pipe the strings through
0337: * @since MMBase-1.8
0338: */
0339:
0340: public static Object wrap(final Object o,
0341: final CharTransformer escaper) {
0342: if (o == null) {
0343: return escape(escaper, "");
0344: } else if (o instanceof Unwrappable) {
0345: return o;
0346: } else if (o instanceof Node) {
0347: return new NodeMap((Node) o) {
0348:
0349: public Object getValue(String fieldName) {
0350: NodeManager nm = getNodeManager();
0351: if (nm.hasField(fieldName)) {
0352: switch (nm.getField(fieldName).getType()) {
0353: case org.mmbase.bridge.Field.TYPE_NODE:
0354: return wrap(getNodeValue(fieldName),
0355: escaper);
0356: case org.mmbase.bridge.Field.TYPE_DATETIME:
0357: return wrap(getDateValue(fieldName),
0358: escaper);
0359: case org.mmbase.bridge.Field.TYPE_XML:
0360: return wrap(getXMLValue(fieldName), escaper);
0361: default:
0362: return escape(escaper, super
0363: .getStringValue(fieldName));
0364: }
0365: } else {
0366: return escape(escaper, super
0367: .getStringValue(fieldName));
0368: }
0369: }
0370:
0371: public String toString() {
0372: return escape(escaper, "" + node.getNumber());
0373: }
0374: };
0375: } else if (o instanceof Date) {
0376: return new java.util.Date(((Date) o).getTime()) {
0377: private static final long serialVersionUID = 1L; // increase this if object chages.
0378:
0379: public String toString() {
0380: long time = getTime();
0381: return time == -1 ? "-1" : ("" + time / 1000);
0382: }
0383: };
0384: } else if (o instanceof org.w3c.dom.Node) {
0385: // don't know how to wrap
0386: return escape(escaper, XMLWriter.write(
0387: (org.w3c.dom.Node) o, false, true));
0388: } else if (o instanceof org.mmbase.bridge.NodeList) {
0389: return new NodeListWrapper((org.mmbase.bridge.NodeList) o,
0390: escaper);
0391: } else if (o instanceof List) {
0392: return new ListWrapper((List) o, escaper);
0393: } else if (o instanceof byte[]) {
0394: return escape(escaper, new String((byte[]) o));
0395: } else if (o instanceof String) {
0396: return escape(escaper, (String) o);
0397: } else if (o instanceof CharSequence) {
0398: return new StringWrapper((CharSequence) o, escaper);
0399: } else {
0400: return o;
0401: }
0402:
0403: }
0404:
0405: private static String escape(CharTransformer escaper,
0406: CharSequence string) {
0407: if (escaper != null) {
0408: return escaper.transform(string.toString());
0409: } else {
0410: return string.toString();
0411: }
0412: }
0413:
0414: /**
0415: * When you want to undo the wrapping, this method can be used.
0416: * @since MMBase-1.8
0417: */
0418: public static Object unWrap(final Object o) {
0419: if (o instanceof NodeWrapper) {
0420: return ((NodeWrapper) o).getNode();
0421: } else if (o instanceof NodeListWrapper) {
0422: return ((NodeListWrapper) o).getCollection();
0423: } else if (o instanceof ListWrapper) {
0424: return ((ListWrapper) o).getList();
0425: } else if (o instanceof StringWrapper) {
0426: return ((StringWrapper) o).getString();
0427: } else {
0428: return o;
0429: }
0430: }
0431:
0432: /**
0433: * Convert an object to a List.
0434: * A String is split up (as if it was a comma-separated String).
0435: * Individual objects are wrapped and returned as Lists with one item.
0436: * <code>null</code> and the empty string are returned as an empty list.
0437: * @param o the object to convert
0438: * @return the converted value as a <code>List</code>
0439: * @since MMBase-1.7
0440: */
0441: public static List toList(Object o) {
0442: return toList(o, ",");
0443: }
0444:
0445: /**
0446: * As {@link #toList(Object)} but with one extra argument.
0447: *
0448: * @param delimiter Regexp to use when splitting up the string if the object is a String. <code>null</code> or the empty string mean the default, which is a comma.
0449: * @since MMBase-1.8
0450: */
0451: public static List toList(Object o, String delimiter) {
0452: if (o instanceof List) {
0453: return (List) o;
0454: } else if (o instanceof Collection) {
0455: return new ArrayList((Collection) o);
0456: } else if (o instanceof String) {
0457: if ("".equals(delimiter) || delimiter == null)
0458: delimiter = ",";
0459: return StringSplitter.split((String) o, delimiter);
0460: } else if (o instanceof Map) {
0461: return new ArrayList(((Map) o).entrySet());
0462: } else {
0463: if (o == null) {
0464: return Collections.emptyList();
0465: }
0466: return Collections.singletonList(o);
0467: }
0468: }
0469:
0470: /**
0471: * @since MMBase-1.8
0472: */
0473: public static Map toMap(Object o) {
0474: if (o instanceof Map) {
0475: return (Map) o;
0476: } else if (o instanceof org.mmbase.util.functions.Parameters) {
0477: return ((org.mmbase.util.functions.Parameters) o).toMap();
0478: } else if (o instanceof Collection) {
0479: Map result = new HashMap();
0480: Iterator i = ((Collection) o).iterator();
0481: while (i.hasNext()) {
0482: Object n = i.next();
0483: if (n instanceof Map.Entry) {
0484: Map.Entry entry = (Map.Entry) n;
0485: result.put(entry.getKey(), entry.getValue());
0486: } else {
0487: result.put(n, n);
0488: }
0489: }
0490: return result;
0491: } else if (o instanceof Node) {
0492: return new NodeMap((Node) o);
0493: } else {
0494: return Collections.singletonMap(o, o);
0495: }
0496: }
0497:
0498: /**
0499: * Transforms an object to a collection. If the object is a collection already, then nothing
0500: * happens. If it is a Map, then the 'entry set' is returned. A string is interpreted as a
0501: * comma-separated list of strings. Other objects are wrapped in an ArrayList with one element.
0502: *
0503: * @since MMBase-1.8.5
0504: */
0505: public static Collection toCollection(Object o, String delimiter) {
0506: if (o instanceof Collection) {
0507: return (Collection) o;
0508: } else if (o instanceof Map) {
0509: return ((Map) o).entrySet();
0510: } else if (o instanceof String) {
0511: if ("".equals(delimiter) || delimiter == null)
0512: delimiter = ",";
0513: return StringSplitter.split((String) o, delimiter);
0514: } else if (o instanceof Object[]) {
0515: return Arrays.asList((Object[]) o);
0516: } else {
0517: if (o == null) {
0518: return Collections.emptyList();
0519: }
0520: return Collections.singletonList(o);
0521: }
0522: }
0523:
0524: /**
0525: * @since MMBase-1.8
0526: */
0527: public static Collection toCollection(Object o) {
0528: return toCollection(o, ",");
0529: }
0530:
0531: /**
0532: * Convert the value to a <code>Document</code> object.
0533: * If the value is not itself a Document, the method attempts to
0534: * attempts to convert the String value into an XML.
0535: * A <code>null</code> value is returned as <code>null</code>.
0536: * If the value cannot be converted, this method throws an IllegalArgumentException.
0537: * @param o the object to be converted to an XML document
0538: * @return the value as a DOM Element or <code>null</code>
0539: * @throws IllegalArgumentException if the value could not be converted
0540: * @since MMBase-1.6
0541: */
0542: static public Document toXML(Object o) {
0543: if (o == null)
0544: return null;
0545: if (!(o instanceof Document)) {
0546: //do conversion from String to Document...
0547: // This is a laborous action, so we log it on debug.
0548: // It will happen often if the nodes are not cached and so on.
0549: String xmltext = toString(o);
0550: if (log.isDebugEnabled()) {
0551: String msg = xmltext;
0552: if (msg.length() > 84) {
0553: msg = msg.substring(0, 80) + "...";
0554: }
0555: log.debug("Object '" + msg
0556: + "' is not a Document, but a "
0557: + o.getClass().getName() + "");
0558: }
0559: return convertStringToXML(xmltext);
0560: }
0561: return (Document) o;
0562: }
0563:
0564: /**
0565: * Convert an object to a byte array.
0566: * @param obj The object to be converted
0567: * @return the value as an <code>byte[]</code> (binary/blob field)
0568: */
0569: static public byte[] toByte(Object obj) {
0570: if (obj == null) {
0571: return new byte[] {};
0572: } else if (obj instanceof byte[]) {
0573: // was allready unmapped so return the value
0574: return (byte[]) obj;
0575: } else if (obj instanceof org.apache.commons.fileupload.FileItem) {
0576: return ((org.apache.commons.fileupload.FileItem) obj).get();
0577: } else if (obj instanceof InputStream) {
0578: InputStream in = (InputStream) obj;
0579: ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
0580: byte[] buf = new byte[1024];
0581: try {
0582: int tot;
0583: do {
0584: tot = in.read(buf, 0, 1024);
0585: out.write(buf, 0, tot);
0586: } while (tot > 0);
0587: } catch (IOException ioe) {
0588: log.error(ioe);
0589: } finally {
0590: try {
0591: in.close();
0592: } catch (IOException ioe) {
0593: }
0594: }
0595: return out.toByteArray();
0596: } else {
0597: return toString(obj).getBytes();
0598: }
0599: }
0600:
0601: static public InputStream toInputStream(Object obj) {
0602: if (obj instanceof InputStream) {
0603: return (InputStream) obj;
0604: } else {
0605: byte[] bytes = toByte(obj);
0606: return new ByteArrayInputStream(bytes, 0, bytes.length);
0607: }
0608: }
0609:
0610: /**
0611: * Convert an object to an Node.
0612: * If the value is Numeric, the method
0613: * tries to obtain the mmbase object with that number.
0614: * A <code>Map</code> returns a virtual <code>Node</code> representing the map, (a
0615: * {@link MapNode}).
0616: * All remaining situations return the node with the alias <code>i.toString()</code>, which can
0617: * be <code>null</code> if no node which such an alias.
0618: * @param i the object to convert
0619: * @param cloud the Cloud to use for loading a node
0620: * @return the value as a <code>Node</code>
0621: * @since MMBase-1.7
0622: */
0623: public static Node toNode(Object i, Cloud cloud) {
0624: Node res = null;
0625: if (i instanceof Node) {
0626: res = (Node) i;
0627: } else if (i instanceof Number) {
0628: int nodenumber = ((Number) i).intValue();
0629: if (nodenumber != -1) {
0630: res = cloud.getNode(nodenumber);
0631: }
0632: } else if (i instanceof Map) {
0633: res = new MapNode((Map) i, cloud);
0634: } else if (i != null && !i.equals("")) {
0635: res = cloud.getNode(i.toString());
0636: }
0637: return res;
0638: }
0639:
0640: /**
0641: * Convert an object to an <code>int</code>.
0642: * Boolean values return 0 for false, 1 for true.
0643: * String values are parsed to a number, if possible.
0644: * If a value is an Node, it's number field is returned.
0645: * All remaining values return the provided default value.
0646: * @param i the object to convert
0647: * @param def the default value if conversion is impossible
0648: * @return the converted value as an <code>int</code>
0649: * @since MMBase-1.7
0650: */
0651: static public int toInt(Object i, int def) {
0652: int res = def;
0653: if (i instanceof Node) {
0654: res = ((Node) i).getNumber();
0655: } else if (i instanceof Boolean) {
0656: res = ((Boolean) i).booleanValue() ? 1 : 0;
0657: } else if (i instanceof Date) {
0658: long timeValue = ((Date) i).getTime();
0659:
0660: if (timeValue != -1)
0661: timeValue /= 1000;
0662:
0663: if (timeValue > Integer.MAX_VALUE) {
0664: res = Integer.MAX_VALUE;
0665: } else if (timeValue < Integer.MIN_VALUE) {
0666: res = Integer.MIN_VALUE;
0667: } else {
0668: res = (int) timeValue;
0669: }
0670: } else if (i instanceof Number) {
0671: long l = ((Number) i).longValue();
0672: if (l > Integer.MAX_VALUE) {
0673: res = Integer.MAX_VALUE;
0674: } else if (l < Integer.MIN_VALUE) {
0675: res = Integer.MIN_VALUE;
0676: } else {
0677: res = (int) l;
0678: }
0679: } else if (i != null) {
0680: try {
0681: res = Integer.parseInt("" + i);
0682: } catch (NumberFormatException e) {
0683: // not an integer? perhaps it is a float or double represented as String.
0684: try {
0685: res = toInt(Double.valueOf("" + i), def); // recursive to hit the check on MAX_VALUE/MIN_VALUE also here.
0686: } catch (NumberFormatException ex) {
0687: // try if the value is a string representing a boolean.
0688: if (i instanceof String) {
0689: String s = ((String) i).toLowerCase();
0690: if (s.equals("true") || s.equals("yes")) {
0691: res = 1;
0692: } else if (s.equals("false") || s.equals("no")) {
0693: res = 0;
0694: }
0695: }
0696: }
0697: }
0698: }
0699: return res;
0700: }
0701:
0702: /**
0703: * Convert an object to an <code>int</code>.
0704: * Boolean values return 0 for false, 1 for true.
0705: * String values are parsed to a number, if possible.
0706: * If a value is a Node, it's number field is returned.
0707: * All remaining values return -1.
0708: * @param i the object to convert
0709: * @return the converted value as an <code>int</code>
0710: */
0711: static public int toInt(Object i) {
0712: return toInt(i, -1);
0713: }
0714:
0715: /**
0716: * Convert an object to a <code>boolean</code>.
0717: * If the value is numeric, this call returns <code>true</code>
0718: * if the value is a positive, non-zero, value. In other words, values '0'
0719: * and '-1' are considered <code>false</code>.
0720: * If the value is a string, this call returns <code>true</code> if
0721: * the value is "true" or "yes" (case-insensitive).
0722: * In all other cases (including calling byte fields), <code>false</code>
0723: * is returned.
0724: * @param b the object to convert
0725: * @return the converted value as a <code>boolean</code>
0726: */
0727: static public boolean toBoolean(Object b) {
0728: if (b == null) {
0729: return false;
0730: } else if (b instanceof Boolean) {
0731: return ((Boolean) b).booleanValue();
0732: } else if (b instanceof Number) {
0733: return ((Number) b).doubleValue() > 0;
0734: } else if (b instanceof Node) {
0735: return true; // return true if a NODE is filled
0736: } else if (b instanceof Date) {
0737: return ((Date) b).getTime() != -1;
0738: } else if (b instanceof Document) {
0739: return false; // undefined
0740: } else if (b instanceof String) {
0741: // note: we don't use Boolean.valueOf() because that only captures
0742: // the value "true"
0743: String s = ((String) b).toLowerCase();
0744: return s.equals("true") || s.equals("yes") || s.equals("1");
0745: } else {
0746: return false;
0747: }
0748: }
0749:
0750: /**
0751: * Convert an object to an Integer.
0752: * Boolean values return 0 for false, 1 for true.
0753: * String values are parsed to a number, if possible.
0754: * All remaining values return -1.
0755: * @param i the object to convert
0756: * @return the converted value as a <code>Integer</code>
0757: */
0758: static public Integer toInteger(Object i) {
0759: if (i instanceof Integer) {
0760: return (Integer) i;
0761: } else {
0762: return Integer.valueOf(toInt(i));
0763: }
0764: }
0765:
0766: /**
0767: * Convert an object to a <code>long</code>.
0768: * Boolean values return 0 for false, 1 for true.
0769: * String values are parsed to a number, if possible.
0770: * All remaining values return the provided default value.
0771: * @param i the object to convert
0772: * @param def the default value if conversion is impossible
0773: * @return the converted value as a <code>long</code>
0774: * @since MMBase-1.7
0775: */
0776: static public long toLong(Object i, long def) {
0777: long res = def;
0778: if (i instanceof Boolean) {
0779: res = ((Boolean) i).booleanValue() ? 1 : 0;
0780: } else if (i instanceof Number) {
0781: res = ((Number) i).longValue();
0782: } else if (i instanceof Date) {
0783: res = ((Date) i).getTime();
0784: if (res != -1)
0785: res /= 1000;
0786: } else if (i instanceof Node) {
0787: res = ((Node) i).getNumber();
0788: } else if (i != null) {
0789: if (i instanceof String) {
0790: String s = ((String) i).toLowerCase();
0791: if (s.equals("true") || s.equals("yes")) {
0792: return 1;
0793: } else if (s.equals("false") || s.equals("no")) {
0794: return 0;
0795: }
0796: }
0797: try {
0798: res = Long.parseLong("" + i);
0799: } catch (NumberFormatException e) {
0800: // not an integer? perhaps it is a float or double represented as String.
0801: try {
0802: res = Double.valueOf("" + i).longValue();
0803: } catch (NumberFormatException ex) {
0804: // give up, fall back to default.
0805: }
0806: }
0807: }
0808: return res;
0809: }
0810:
0811: /**
0812: * Convert an object to a <code>long</code>.
0813: * Boolean values return 0 for false, 1 for true.
0814: * String values are parsed to a number, if possible.
0815: * All remaining values return -1.
0816: * @param i the object to convert
0817: * @return the converted value as a <code>long</code>
0818: * @since MMBase-1.7
0819: */
0820: static public long toLong(Object i) {
0821: return toLong(i, -1);
0822: }
0823:
0824: /**
0825: * Convert an object to an <code>float</code>.
0826: * Boolean values return 0 for false, 1 for true.
0827: * String values are parsed to a number, if possible.
0828: * All remaining values return the default value.
0829: * @param i the object to convert
0830: * @param def the default value if conversion is impossible
0831: * @return the converted value as a <code>float</code>
0832: */
0833: static public float toFloat(Object i, float def) {
0834: float res = def;
0835: if (i instanceof Boolean) {
0836: res = ((Boolean) i).booleanValue() ? 1 : 0;
0837: } else if (i instanceof Number) {
0838: res = ((Number) i).floatValue();
0839: } else if (i instanceof Date) {
0840: res = ((Date) i).getTime();
0841: if (res != -1)
0842: res = res / 1000;
0843: } else if (i instanceof Node) {
0844: res = ((Node) i).getNumber();
0845: } else if (i != null) {
0846: if (i instanceof String) {
0847: String s = ((String) i).toLowerCase();
0848: if (s.equals("true") || s.equals("yes")) {
0849: res = 1;
0850: } else if (s.equals("false") || s.equals("no")) {
0851: res = 0;
0852: }
0853: }
0854: try {
0855: res = Float.parseFloat("" + i);
0856: } catch (NumberFormatException e) {
0857: // use default
0858: }
0859: }
0860: return res;
0861: }
0862:
0863: /**
0864: * Convert an object to an <code>float</code>.
0865: * Boolean values return 0 for false, 1 for true.
0866: * String values are parsed to a number, if possible.
0867: * All remaining values return -1.
0868: * @param i the object to convert
0869: * @return the converted value as a <code>float</code>
0870: */
0871: static public float toFloat(Object i) {
0872: return toFloat(i, -1);
0873: }
0874:
0875: /**
0876: * Convert an object to an <code>double</code>.
0877: * Boolean values return 0 for false, 1 for true.
0878: * String values are parsed to a number, if possible.
0879: * All remaining values return the default value.
0880: * @param i the object to convert
0881: * @param def the default value if conversion is impossible
0882: * @return the converted value as a <code>double</code>
0883: */
0884: static public double toDouble(Object i, double def) {
0885: double res = def;
0886: if (i instanceof Boolean) {
0887: res = ((Boolean) i).booleanValue() ? 1 : 0;
0888: } else if (i instanceof Number) {
0889: res = ((Number) i).doubleValue();
0890: } else if (i instanceof Date) {
0891: res = ((Date) i).getTime();
0892: if (res != -1)
0893: res = res / 1000;
0894: } else if (i instanceof Node) {
0895: res = ((Node) i).getNumber();
0896: } else if (i != null) {
0897: try {
0898: res = Double.parseDouble("" + i);
0899: } catch (NumberFormatException e) {
0900: // try if the value is a string representing a boolean.
0901: if (i instanceof String) {
0902: String s = ((String) i).toLowerCase();
0903: if (s.equals("true") || s.equals("yes")) {
0904: res = 1;
0905: } else if (s.equals("false") || s.equals("no")) {
0906: res = 0;
0907: }
0908: }
0909: }
0910: }
0911: return res;
0912: }
0913:
0914: /**
0915: * Convert an object to an <code>double</code>.
0916: * Boolean values return 0 for false, 1 for true.
0917: * String values are parsed to a number, if possible.
0918: * All remaining values return -1.
0919: * @param i the object to convert
0920: * @return the converted value as a <code>double</code>
0921: */
0922: static public double toDouble(Object i) {
0923: return toDouble(i, -1);
0924: }
0925:
0926: /**
0927: * Convert an object to a <code>Date</code>.
0928: * String values are parsed to a date, if possible.
0929: * Numeric values are assumed to represent number of seconds since 1970.
0930: * All remaining values return 1969-12-31 23:59 GMT.
0931: * @param d the object to convert
0932: * @return the converted value as a <code>Date</code>, never <code>null</code>
0933: * @since MMBase-1.7
0934: */
0935: static public Date toDate(Object d) {
0936: if (d == null)
0937: return new Date(-1);
0938: Date date = null;
0939:
0940: if (d instanceof Date) {
0941: date = (Date) d;
0942: } else {
0943: try {
0944: long dateInSeconds = -1;
0945: if (d instanceof Number) {
0946: dateInSeconds = ((Number) d).longValue();
0947: } else if (d instanceof Document) {
0948: // impossible
0949: dateInSeconds = -1;
0950: } else if (d instanceof Boolean) {
0951: dateInSeconds = -1;
0952: } else if (d instanceof Collection) {
0953: // impossible
0954: dateInSeconds = -1;
0955: } else if (d instanceof Node) {
0956: // impossible
0957: dateInSeconds = -1;
0958: } else if (d != null) {
0959: d = toString(d);
0960: if (d.equals("")) {
0961: return new Date(-1);
0962: }
0963: dateInSeconds = Long.parseLong((String) d);
0964: } else {
0965: dateInSeconds = -1;
0966: }
0967: if (dateInSeconds == -1) {
0968: date = new Date(-1);
0969: } else if (dateInSeconds > Long.MAX_VALUE / 1000) {
0970: date = new Date(Long.MAX_VALUE); // or should this throw an exception?
0971: } else if (dateInSeconds < Long.MIN_VALUE / 1000) {
0972: date = new Date(Long.MIN_VALUE); // or should this throw an exception?
0973: } else {
0974: date = new Date(dateInSeconds * 1000);
0975: }
0976: } catch (NumberFormatException e) {
0977: try {
0978: date = DynamicDate.getInstance((String) d);
0979: } catch (org.mmbase.util.dateparser.ParseException pe) {
0980: log.error("Parser exception in " + d, pe);
0981: return new Date(-1);
0982: } catch (Error per) {
0983: throw new Error("Parser error in " + d, per);
0984: }
0985: }
0986: }
0987: return date;
0988: }
0989:
0990: static DocumentBuilder DOCUMENTBUILDER;
0991: static {
0992: try {
0993: DocumentBuilderFactory dfactory = DocumentBuilderFactory
0994: .newInstance();
0995: dfactory.setValidating(false);
0996: dfactory.setNamespaceAware(true);
0997: DOCUMENTBUILDER = dfactory.newDocumentBuilder();
0998: DOCUMENTBUILDER.setEntityResolver(new XMLEntityResolver(
0999: false));
1000: } catch (ParserConfigurationException pce) {
1001: log.error("[sax parser]: " + pce.toString(), pce);
1002: }
1003: }
1004:
1005: /**
1006: * Convert a String value to a Document
1007: * @param value The current value (can be null)
1008: * @return the value as a DOM Element or <code>null</code>
1009: * @throws IllegalArgumentException if the value could not be converted
1010: */
1011: static private Document convertStringToXML(String value) {
1012: if (value == null) {
1013: return null;
1014: }
1015: if (log.isTraceEnabled()) {
1016: log.trace("using xml string:\n" + value);
1017: }
1018: try {
1019: Document doc;
1020: final XMLErrorHandler errorHandler = new XMLErrorHandler(
1021: false, org.mmbase.util.XMLErrorHandler.NEVER);
1022: synchronized (DOCUMENTBUILDER) {
1023: // dont log errors, and try to process as much as possible...
1024: DOCUMENTBUILDER.setErrorHandler(errorHandler);
1025: // ByteArrayInputStream?
1026: // Yes, in contradiction to what one would think, XML are bytes, rather then characters.
1027: doc = DOCUMENTBUILDER
1028: .parse(new java.io.ByteArrayInputStream(value
1029: .getBytes("UTF-8")));
1030: }
1031: if (log.isTraceEnabled()) {
1032: log.trace("parsed: "
1033: + XMLWriter.write(doc, false, true));
1034: }
1035: if (!errorHandler.foundNothing()) {
1036: throw new IllegalArgumentException("xml invalid:\n"
1037: + errorHandler.getMessageBuffer()
1038: + "for xml:\n" + value);
1039: }
1040: return doc;
1041: } catch (org.xml.sax.SAXException se) {
1042: if (log.isDebugEnabled()) {
1043: log.debug("[sax] not well formed xml: " + se.toString()
1044: + "(" + se.getMessage() + ")\n"
1045: + Logging.stackTrace(se));
1046: }
1047: return convertStringToXML("<p>"
1048: + Encode.encode("ESCAPE_XML", value) + "</p>"); // Should _always_ be sax-compliant.
1049: } catch (java.io.IOException ioe) {
1050: String msg = "[io] not well formed xml: " + ioe.toString()
1051: + "\n" + Logging.stackTrace(ioe);
1052: throw new IllegalArgumentException(msg);
1053: }
1054: }
1055:
1056: /*
1057: * Wraps a List with an 'Escaper'.
1058: * @since MMBase-1.8
1059: */
1060: public static class ListWrapper extends AbstractList {
1061: private final List list;
1062: private final CharTransformer escaper;
1063:
1064: ListWrapper(List l, CharTransformer e) {
1065: list = l;
1066: escaper = e;
1067: }
1068:
1069: public Object get(int index) {
1070: return Casting.wrap(list.get(index), escaper);
1071: }
1072:
1073: public int size() {
1074: return list.size();
1075: }
1076:
1077: public Object set(int index, Object value) {
1078: return list.set(index, value);
1079: }
1080:
1081: public void add(int index, Object value) {
1082: list.add(index, value);
1083: }
1084:
1085: public Object remove(int index) {
1086: return list.remove(index);
1087: }
1088:
1089: public boolean isEmpty() {
1090: return list.isEmpty();
1091: }
1092:
1093: public boolean contains(Object o) {
1094: return list.contains(o);
1095: }
1096:
1097: public Object[] toArray() {
1098: return list.toArray();
1099: }
1100:
1101: public Object[] toArray(Object[] a) {
1102: return list.toArray(a);
1103: }
1104:
1105: public Iterator iterator() {
1106: return list.iterator();
1107: }
1108:
1109: public ListIterator listIterator() {
1110: return list.listIterator();
1111: }
1112:
1113: public String toString() {
1114: StringBuilder buf = new StringBuilder();
1115: Iterator i = list.iterator();
1116: boolean hasNext = i.hasNext();
1117: while (hasNext) {
1118: Casting.toStringBuilder(buf, i.next());
1119: hasNext = i.hasNext();
1120: if (hasNext) {
1121: buf.append(',');
1122: }
1123: }
1124: return buf.toString();
1125: }
1126:
1127: public List getList() {
1128: return list;
1129: }
1130: }
1131:
1132: /**
1133: * @since MMBase-1.9
1134: */
1135: public static class NodeListWrapper extends
1136: org.mmbase.bridge.util.CollectionNodeList {
1137: private final CharTransformer escaper;
1138:
1139: NodeListWrapper(org.mmbase.bridge.NodeList list,
1140: CharTransformer e) {
1141: super (list);
1142: escaper = e;
1143: }
1144:
1145: public Node get(int index) {
1146: return (Node) Casting.wrap(super .get(index), escaper);
1147: }
1148:
1149: public String toString() {
1150: StringBuilder buf = new StringBuilder();
1151: Iterator<Node> i = iterator();
1152: boolean hasNext = i.hasNext();
1153: while (hasNext) {
1154: Casting.toStringBuilder(buf, i.next());
1155: hasNext = i.hasNext();
1156: if (hasNext) {
1157: buf.append(',');
1158: }
1159: }
1160: return buf.toString();
1161: }
1162:
1163: }
1164:
1165: /**
1166: * Wraps a String with an 'Escaper'.
1167: * @since MMBase-1.8
1168: */
1169: public static class StringWrapper implements CharSequence {
1170: private final CharTransformer escaper;
1171: private final CharSequence string;
1172: private String escaped = null;
1173:
1174: StringWrapper(CharSequence s, CharTransformer e) {
1175: escaper = e;
1176: string = s;
1177:
1178: }
1179:
1180: public char charAt(int index) {
1181: toString();
1182: return escaped.charAt(index);
1183: }
1184:
1185: public int length() {
1186: toString();
1187: return escaped.length();
1188: }
1189:
1190: public CharSequence subSequence(int start, int end) {
1191: toString();
1192: return escaped.subSequence(start, end);
1193: }
1194:
1195: public String toString() {
1196: if (escaped == null)
1197: escaped = escape(escaper, string);
1198: return escaped;
1199: }
1200:
1201: public CharSequence getString() {
1202: return string;
1203: }
1204: }
1205:
1206: /**
1207: * Clases implementint this will not be wrapped by {@link #wrap}, even if the e.g. are CharSequence.
1208: * @since MMBase-1.9
1209: */
1210: public static interface Unwrappable {
1211: }
1212:
1213: /**
1214: * @since MMBase-1.9
1215: */
1216: public static boolean equals(Object o1, Object o2) {
1217: if (o1 == null)
1218: return o2 == null;
1219:
1220: if (o1 instanceof org.w3c.dom.Node) {
1221: return (o2 instanceof org.w3c.dom.Node && ((org.w3c.dom.Node) o1)
1222: .isEqualNode((org.w3c.dom.Node) o2));
1223: } else {
1224: return o1.equals(o2);
1225: }
1226: }
1227:
1228: }
|