All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.io.Util Maven / Gradle / Ivy

There is a newer version: 7.0.164
Show newest version
/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 Oracle, 500 Oracle Parkway, Redwood Shores
 * CA 94065 USA or visit www.oracle.com if you need additional information or
 * have any questions.
 */

package com.codename1.io;

import com.codename1.components.InfiniteProgress;
import com.codename1.impl.CodenameOneImplementation;
import com.codename1.io.Externalizable;
import com.codename1.io.IOProgressListener;
import com.codename1.io.FileSystemStorage;
import com.codename1.io.Storage;
import com.codename1.io.gzip.GZConnectionRequest;
import com.codename1.l10n.DateFormat;
import com.codename1.l10n.L10NManager;
import com.codename1.l10n.ParseException;
import com.codename1.l10n.SimpleDateFormat;
import com.codename1.properties.PropertyBusinessObject;
import com.codename1.ui.CN;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.EncodedImage;
import com.codename1.ui.Image;
import com.codename1.ui.events.ActionListener;
import com.codename1.util.AsyncResource;
import com.codename1.util.Base64;
import com.codename1.util.CallbackAdapter;
import com.codename1.util.EasyThread;
import com.codename1.util.FailureCallback;
import com.codename1.util.OnComplete;
import com.codename1.util.SuccessCallback;
import com.codename1.util.Wrapper;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

/**
 * Various utility methods used for HTTP/IO operations
 *
 * @author Shai Almog
 */
public class Util {
    private static CodenameOneImplementation implInstance;
    private static Hashtable externalizables = new Hashtable();

    private static boolean charArrayBugTested;
    private static boolean charArrayBug;
    private static final Random downloadUrlSafelyRandom = new Random(System.currentTimeMillis()); 

    static {
        register("EncodedImage", EncodedImage.class);
    }
    
    /**
     * Fix for RFE 427: http://java.net/jira/browse/LWUIT-427
     * Allows determining chars that should not be encoded
     */
    private static String ignoreCharsWhenEncoding = "";

    /**
     *  These chars will not be encoded by the encoding method in this class
     * as requested in RFE 427 http://java.net/jira/browse/LWUIT-427
     * @param s set of characters to skip when encoding
     */
    public static void setIgnorCharsWhileEncoding(String s) {
        ignoreCharsWhenEncoding = s;
    }

    /**
     *  These chars will not be encoded by the encoding method in this class
     * as requested in RFE 427 http://java.net/jira/browse/LWUIT-427
     * @return chars skipped
     */
    public static String getIgnorCharsWhileEncoding() {
        return ignoreCharsWhenEncoding;
    }


    /**
     * Copy the input stream into the output stream, closes both streams when finishing or in
     * a case of an exception
     * 
     * @param i source
     * @param o destination
     */
    public static void copy(InputStream i, OutputStream o) throws IOException {
        copy(i, o, 8192);
    }

    /**
     * Copy the input stream into the output stream, without closing the streams when done
     *
     * @param i source
     * @param o destination
     * @param bufferSize the size of the buffer, which should be a power of 2 large enough
     */
    public static void copyNoClose(InputStream i, OutputStream o, int bufferSize) throws IOException {
        copyNoClose(i, o, bufferSize, null);
    }
    
    /**
     * Copy the input stream into the output stream, without closing the streams when done
     *
     * @param i source
     * @param o destination
     * @param bufferSize the size of the buffer, which should be a power of 2 large enough
     * @param callback called after each copy step
     */
    public static void copyNoClose(InputStream i, OutputStream o, int bufferSize, IOProgressListener callback) throws IOException {

        byte[] buffer = new byte[bufferSize];
        int size = i.read(buffer);
        int total = 0;
        while(size > -1) {
            o.write(buffer, 0, size);
            if(callback != null) {
                callback.ioStreamUpdate(i, total += size);
            }
            size = i.read(buffer);
        }
    }
    
    /**
     * Copy the input stream into the output stream, closes both streams when finishing or in
     * a case of an exception
     *
     * @param i source
     * @param o destination
     * @param bufferSize the size of the buffer, which should be a power of 2 large enough
     */
    public static void copy(InputStream i, OutputStream o, int bufferSize) throws IOException {
        try {
            copyNoClose(i, o, bufferSize);
        } finally {
            Util.getImplementation().cleanup(o);
            Util.getImplementation().cleanup(i);
        }
    }

    /**
     * Closes the object (connection, stream etc.) without throwing any exception, even if the
     * object is null
     *
     * @param o Connection, Stream or other closeable object
     */
    public static void cleanup(Object o) {
        Util.getImplementation().cleanup(o);
    }

    /**
     * Reads an input stream to a string
     * 
     * @param i the input stream
     * @return a UTF-8 string
     * @throws IOException thrown by the stream
     */
    public static String readToString(InputStream i) throws IOException {
        return readToString(i, "UTF-8");
    }
    
    /**
     * Reads the contents of a file to a string.
     * @param file The file to read.
     * @param charset The Charset to use to write the file.
     * @return The string read from the file.
     * 
     * @throws IOException If the file does not exist, or cannot be read for some reason.
     * 
     * @since 8.0
     */
    public static String readToString(File file, String charset) throws IOException {
        if (charset == null) charset = "UTF-8";
        if (!file.exists()) {
            throw new IOException("Failed to read file "+file+" because it does not exist.");
        }
        return Util.readToString(FileSystemStorage.getInstance().openInputStream(file.getAbsolutePath()), charset);
    }
    
    /**
     * Reads the contents of a file to a string.  Uses UTF-8 encoding.
     * @param file The file to read.
     * @return The string read from the file.
     * 
     * @throws IOException If the file does not exist, or cannot be read for some reason.
     * 
     * @since 8.0
     */
    public static String readToString(File file) throws IOException {
        return readToString(file, "UTF-8");
    }
    
    /**
     * Writes a string to a file using UTF-8 encoding.
     * @param file The file to write to.
     * @param contents The contents to write to the file.
     * @throws IOException If it cannot write to the file for some reason.
     * @since 8.0
     */
    public static void writeStringToFile(File file, String contents) throws IOException {
        writeStringToFile(file, contents, "UTF-8");
    }
    
    /**
     * Writes a string to a file.
     * @param file The file to write to.
     * @param contents The contents to write to the file.
     * @param charset The charset to use.  If null, it defaults to UTF-8
     * @throws IOException If it cannot write to the file for some reason.
     * @since 8.0
     */
    public static void writeStringToFile(File file, String contents, String charset) throws IOException {
        if (charset == null) charset= "UTF-8";
        OutputStream output = null;
        try {
            output = FileSystemStorage.getInstance().openOutputStream(file.getAbsolutePath());
            output.write(contents.getBytes(charset));
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (Exception ex){}
            }
        }
    }

    /**
     * Reads an input stream to a string
     * 
     * @param i the input stream
     * @param encoding the encoding of the stream
     * @return a string
     * @throws IOException thrown by the stream
     */
    public static String readToString(InputStream i, String encoding) throws IOException {
        byte[] b = readInputStream(i);
        return new String(b, 0, b.length, encoding);
    }
    
    /**
     * Reads a reader to a string
     * 
     * @param i the input stream
     * @param encoding the encoding of the stream
     * @return a string
     * @throws IOException thrown by the stream
     * @since 7.0
     */
    public static String readToString(Reader reader) throws IOException {
        StringBuilder sb = new StringBuilder();
        char[] buf = new char[1024];
        int len;
        while ((len = reader.read(buf)) != -1) {
            sb.append(buf, 0, len);
        }
        return sb.toString();
    }

    /**
     * Converts a small input stream to a byte array
     *
     * @param i the stream to convert
     * @return byte array of the content of the stream
     */
    public static byte[] readInputStream(InputStream i) throws IOException {
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        copy(i, b);
        return b.toByteArray();
    }

    /**
     * 

Registers this externalizable so readObject will be able to load such objects.

*

* The sample below demonstrates the usage and registration of the {@link com.codename1.io.Externalizable} interface: *

* * * * @param e the externalizable instance */ public static void register(Externalizable e) { externalizables.put(e.getObjectId(), e.getClass()); } /** *

Registers this externalizable so readObject will be able to load such objects.

* *

* The sample below demonstrates the usage and registration of the {@link com.codename1.io.Externalizable} interface: *

* * * @param id id of the externalizable * @param c the class for the externalizable */ public static void register(String id, Class c) { externalizables.put(id, c); } /** *

Writes an object to the given output stream, notice that it should be externalizable or one of * the supported types.

* *

* The sample below demonstrates the usage and registration of the {@link com.codename1.io.Externalizable} interface: *

* * * @param o the object to write which can be null * @param out the destination output stream * @throws IOException thrown by the stream */ public static void writeObject(Object o, DataOutputStream out) throws IOException { if(o == null) { out.writeBoolean(false); return; } out.writeBoolean(true); if(o instanceof Externalizable) { Externalizable e = (Externalizable)o; out.writeUTF(e.getObjectId()); out.writeInt(e.getVersion()); e.externalize(out); return; } if(o instanceof PropertyBusinessObject) { Externalizable e = ((PropertyBusinessObject)o).getPropertyIndex().asExternalizable(); out.writeUTF(e.getObjectId()); out.writeInt(e.getVersion()); e.externalize(out); return; } if(o instanceof Vector) { Vector v = (Vector)o; out.writeUTF("java.util.Vector"); int size = v.size(); out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { writeObject(v.elementAt(iter), out); } return; } if(o instanceof Set) { Collection v = (Collection)o; out.writeUTF("java.util.Set"); int size = v.size(); out.writeInt(size); for(Object cur : v) { writeObject(cur, out); } return; } if(o instanceof Collection) { Collection v = (Collection)o; out.writeUTF("java.util.Collection"); int size = v.size(); out.writeInt(size); for(Object cur : v) { writeObject(cur, out); } return; } if(o instanceof Hashtable) { Hashtable v = (Hashtable)o; out.writeUTF("java.util.Hashtable"); out.writeInt(v.size()); Enumeration k = v.keys(); while(k.hasMoreElements()) { Object key = k.nextElement(); writeObject(key, out); writeObject(v.get(key), out); } return; } if(o instanceof Map) { Map v = (Map)o; out.writeUTF("java.util.Map"); out.writeInt(v.size()); for(Object key : v.keySet()) { writeObject(key, out); writeObject(v.get(key), out); } return; } if(o instanceof String) { String v = (String)o; out.writeUTF("String"); out.writeUTF(v); return; } if(o instanceof Date) { Date v = (Date)o; out.writeUTF("Date"); out.writeLong(v.getTime()); return; } if(o instanceof Integer) { Integer v = (Integer)o; out.writeUTF("int"); out.writeInt(v.intValue()); return; } if(o instanceof Long) { Long v = (Long)o; out.writeUTF("long"); out.writeLong(v.longValue()); return; } if(o instanceof Byte) { Byte v = (Byte)o; out.writeUTF("byte"); out.writeByte(v.byteValue()); return; } if(o instanceof Short) { Short v = (Short)o; out.writeUTF("short"); out.writeShort(v.shortValue()); return; } if(o instanceof Float) { Float v = (Float)o; out.writeUTF("float"); out.writeFloat(v.floatValue()); return; } if(o instanceof Double) { Double v = (Double)o; out.writeUTF("double"); out.writeDouble(v.doubleValue()); return; } if(o instanceof Boolean) { Boolean v = (Boolean)o; out.writeUTF("bool"); out.writeBoolean(v.booleanValue()); return; } if (o instanceof EncodedImage) { out.writeUTF("EncodedImage"); EncodedImage e = (EncodedImage)o; out.writeInt(e.getWidth()); out.writeInt(e.getHeight()); out.writeBoolean(e.isOpaque()); byte[] b = e.getImageData(); out.writeInt(b.length); out.write(b); return; } if(instanceofObjArray(o)) { Object[] v = (Object[])o; out.writeUTF("ObjectArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { writeObject(v[iter], out); } return; } if(instanceofByteArray(o)) { byte[] v = (byte[])o; out.writeUTF("ByteArray"); int size = v.length; out.writeInt(size); out.write(v); return; } if(instanceofShortArray(o)) { short[] v = (short[])o; out.writeUTF("ShortArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { out.writeShort(v[iter]); } return; } if(instanceofDoubleArray(o)) { double[] v = (double[])o; out.writeUTF("DoubleArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { out.writeDouble(v[iter]); } return; } if(instanceofFloatArray(o)) { float[] v = (float[])o; out.writeUTF("FloatArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { out.writeFloat(v[iter]); } return; } if(instanceofIntArray(o)) { int[] v = (int[])o; out.writeUTF("IntArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { out.writeInt(v[iter]); } return; } if(instanceofLongArray(o)) { long[] v = (long[])o; out.writeUTF("LongArray"); int size = v.length; out.writeInt(size); for(int iter = 0 ; iter < size ; iter++) { out.writeLong(v[iter]); } return; } throw new IOException("Object type not supported: " + o.getClass().getName() + " value: " + o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofObjArray(Object o) { return getImplementation().instanceofObjArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofByteArray(Object o) { return getImplementation().instanceofByteArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofShortArray(Object o) { return getImplementation().instanceofShortArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofLongArray(Object o) { return getImplementation().instanceofLongArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofIntArray(Object o) { return getImplementation().instanceofIntArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofFloatArray(Object o) { return getImplementation().instanceofFloatArray(o); } /** * This method allows working around issue 58 * * @param o object to test * @return true if it matches the state * @deprecated this method serves as a temporary workaround for an XMLVM bug and will be removed * once the bug is fixed */ public static boolean instanceofDoubleArray(Object o) { return getImplementation().instanceofDoubleArray(o); } /** *

Reads an object from the stream, notice that this is the inverse of the * {@link #writeObject(java.lang.Object, java.io.DataOutputStream)}.

* *

* The sample below demonstrates the usage and registration of the {@link com.codename1.io.Externalizable} interface: *

* * * @param input the source input stream * @throws IOException thrown by the stream */ public static Object readObject(DataInputStream input) throws IOException { try { if (!input.readBoolean()) { return null; } String type = input.readUTF(); if ("int".equals(type)) { return new Integer(input.readInt()); } if ("byte".equals(type)) { return new Byte(input.readByte()); } if ("short".equals(type)) { return new Short(input.readShort()); } if ("long".equals(type)) { return new Long(input.readLong()); } if ("float".equals(type)) { return new Float(input.readFloat()); } if ("double".equals(type)) { return new Double(input.readDouble()); } if ("bool".equals(type)) { return new Boolean(input.readBoolean()); } if ("String".equals(type)) { return input.readUTF(); } if ("Date".equals(type)) { return new Date(input.readLong()); } if ("ObjectArray".equals(type)) { Object[] v = new Object[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = readObject(input); } return v; } if ("ByteArray".equals(type)) { byte[] v = new byte[input.readInt()]; input.readFully(v); return v; } if ("LongArray".equals(type)) { long[] v = new long[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = input.readLong(); } return v; } if ("ShortArray".equals(type)) { short[] v = new short[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = input.readShort(); } return v; } if ("DoubleArray".equals(type)) { double[] v = new double[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = input.readDouble(); } return v; } if ("FloatArray".equals(type)) { float[] v = new float[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = input.readFloat(); } return v; } if ("IntArray".equals(type)) { int[] v = new int[input.readInt()]; int vlen = v.length; for (int iter = 0; iter < vlen; iter++) { v[iter] = input.readInt(); } return v; } if ("java.util.Vector".equals(type)) { Vector v = new Vector(); int size = input.readInt(); for (int iter = 0; iter < size; iter++) { v.addElement(readObject(input)); } return v; } if ("java.util.Hashtable".equals(type)) { Hashtable v = new Hashtable(); int size = input.readInt(); for(int iter = 0 ; iter < size ; iter++) { v.put(readObject(input), readObject(input)); } return v; } if ("java.util.Set".equals(type)) { Collection v = new HashSet(); int size = input.readInt(); for (int iter = 0; iter < size; iter++) { v.add(readObject(input)); } return v; } if ("java.util.Collection".equals(type)) { Collection v = new ArrayList(); int size = input.readInt(); for (int iter = 0; iter < size; iter++) { v.add(readObject(input)); } return v; } if ("java.util.Map".equals(type)) { Map v = new HashMap(); int size = input.readInt(); for(int iter = 0 ; iter < size ; iter++) { v.put(readObject(input), readObject(input)); } return v; } if ("EncodedImage".equals(type)) { int width = input.readInt(); int height = input.readInt(); boolean op = input.readBoolean(); byte[] data = new byte[input.readInt()]; input.readFully(data); return EncodedImage.create(data, width, height, op); } Class cls = (Class) externalizables.get(type); if (cls != null) { Object o = cls.newInstance(); if(o instanceof Externalizable) { Externalizable ex = (Externalizable)o; ex.internalize(input.readInt(), input); return ex; } else { PropertyBusinessObject pb = (PropertyBusinessObject)o; pb.getPropertyIndex().asExternalizable().internalize(input.readInt(), input); return pb; } } throw new IOException("Object type not supported: " + type); } catch (InstantiationException ex1) { Log.e(ex1); throw new IOException(ex1.getClass().getName() + ": " + ex1.getMessage()); } catch (IllegalAccessException ex1) { Log.e(ex1); throw new IOException(ex1.getClass().getName() + ": " + ex1.getMessage()); } } /** * Encode a string for HTML requests * * @param str none encoded string * @return encoded string */ public static String encodeUrl(final String str) { return encode(str, "%20"); } /** * Encodes the provided string as a URL (with %20 for spaces). * @param str The URL to encode * @param doNotEncodeChars A string whose characters will not be encoded. * @return */ public static String encodeUrl(final String str, String doNotEncodeChars) { return encode(str.toCharArray(), "%20", doNotEncodeChars); } /** * toCharArray should return a new array always, however some devices might * suffer a bug that allows mutating a String (serious security hole in the JVM) * hence this method simulates the proper behavior * @param s a string * @return the contents of the string as a char array guaranteed to be a copy of the current array */ public static char[] toCharArray(String s) { // toCharArray should return a new array always, however some devices might // suffer a bug that allows mutating a String (serious security hole in the JVM) // hence this method simulates the proper behavior if(!charArrayBugTested) { charArrayBugTested = true; if(s.toCharArray() == s.toCharArray()) { charArrayBug = true; } } if(charArrayBug) { char[] c = new char[s.length()]; System.arraycopy(s.toCharArray(), 0, c, 0, c.length); return c; } return s.toCharArray(); } private static String encode(String str, String spaceChar) { if (str == null) { return null; } return encode(toCharArray(str), spaceChar); } /** * Decodes a String URL encoded URL * * @param s the string * @param enc the encoding (defaults to UTF-8 if null) * @param plusToSpace true if plus signs be converted to spaces * @return a decoded string */ public static String decode(String s, String enc, boolean plusToSpace) { boolean modified = false; if(enc == null || enc.length() == 0) { enc = "UTF-8"; } int numChars = s.length(); StringBuilder sb = new StringBuilder(numChars > 500 ? numChars / 2 : numChars); int i = 0; char c; byte[] bytes = null; while (i < numChars) { c = s.charAt(i); switch (c) { case '+': if(plusToSpace) { sb.append(' '); } else { sb.append('+'); } i++; modified = true; break; case '%': try { if (bytes == null) { bytes = new byte[(numChars - i) / 3]; } int pos = 0; while (((i + 2) < numChars) && (c == '%')) { bytes[pos++] = (byte) Integer.parseInt(s.substring(i + 1, i + 3), 16); i += 3; if (i < numChars) { c = s.charAt(i); } } if ((i < numChars) && (c == '%')) { throw new IllegalArgumentException("Illegal URL % character: " + s); } try { sb.append(new String(bytes, 0, pos, enc)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e.toString()); } } catch (NumberFormatException e) { throw new IllegalArgumentException("Illegal URL encoding: " + s); } modified = true; break; default: sb.append(c); i++; break; } } if(modified) { return sb.toString(); } return s; } private static String encode(char[] buf, String spaceChar) { return encode(buf, spaceChar, null); } private static String encode(char[] buf, String spaceChar, String doNotEncode) { final StringBuilder sbuf = new StringBuilder(buf.length * 3); int blen = buf.length; for (int i = 0; i < blen; i++) { final char ch = buf[i]; if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-' || ch == '_' || ch == '.' || ch == '~' || ch == '!' || ch == '*' || ch == '\'' || ch == '(' || ch == ')' || ignoreCharsWhenEncoding.indexOf(ch) > -1) || (doNotEncode != null && doNotEncode.indexOf(ch) > -1)) { sbuf.append(ch); } else if (ch == ' ') { sbuf.append(spaceChar); } else { appendHex(sbuf, ch); } } return sbuf.toString(); } /** * Encode a string for HTML post requests matching the style used in application/x-www-form-urlencoded * * @param str none encoded string * @return encoded string */ public static String encodeBody(final String str) { return encode(str, "+"); } /** * Encode a string for HTML requests * * @param buf none encoded string * @return encoded string * @deprecated use encodeUrl(char[]) instead */ public static String encodeUrl(final byte[] buf) { char[] b = new char[buf.length]; for(int iter = 0 ; iter < buf.length ; iter++) { b[iter] = (char)buf[iter]; } return encode(b, "%20"); } /** * Encode a string for HTML requests * * @param buf none encoded string * @return encoded string */ public static String encodeUrl(final char[] buf) { return encode(buf, "%20"); } /** * Encode a string for HTML post requests matching the style used in application/x-www-form-urlencoded * * @param buf none encoded string * @return encoded string */ public static String encodeBody(final char[] buf) { return encode(buf, "+"); } /** * Encode a string for HTML post requests matching the style used in application/x-www-form-urlencoded * * @param buf none encoded string * @return encoded string * @deprecated use encodeUrl(char[]) instead */ public static String encodeBody(final byte[] buf) { char[] b = new char[buf.length]; int blen = buf.length; for(int iter = 0 ; iter < blen ; iter++) { b[iter] = (char)buf[iter]; } return encode(b, "+"); } private static void appendHex(StringBuilder sbuf, char ch) { int firstLiteral = ch / 256; int secLiteral = ch % 256; if(firstLiteral == 0 && secLiteral < 127) { sbuf.append("%"); String s = Integer.toHexString(secLiteral).toUpperCase(); if(s.length() == 1) { sbuf.append("0"); } sbuf.append(s); return; } if (ch <= 0x07ff) { // 2 literals unicode firstLiteral = 192 + (firstLiteral << 2) +(secLiteral >> 6); secLiteral=128+(secLiteral & 63); sbuf.append("%"); sbuf.append(Integer.toHexString(firstLiteral).toUpperCase()); sbuf.append("%"); sbuf.append(Integer.toHexString(secLiteral).toUpperCase()); } else { // 3 literals unicode int thirdLiteral = 128 + (secLiteral & 63); secLiteral = 128 + ((firstLiteral % 16) << 2) + (secLiteral >> 6); firstLiteral=224+(firstLiteral>>4); sbuf.append("%"); sbuf.append(Integer.toHexString(firstLiteral).toUpperCase()); sbuf.append("%"); sbuf.append(Integer.toHexString(secLiteral).toUpperCase()); sbuf.append("%"); sbuf.append(Integer.toHexString(thirdLiteral).toUpperCase()); } } /** * Converts a relative url e.g.: /myfile.html to an absolute url * * @param baseURL a source URL whose properties should be used to construct the actual URL * @param relativeURL relative address * @return an absolute URL */ public static String relativeToAbsolute(String baseURL, String relativeURL) { if(relativeURL.startsWith("/")) { return getURLProtocol(baseURL) + "://" + getURLHost(baseURL) + relativeURL; } else { return getURLProtocol(baseURL) + "://" + getURLHost(baseURL) + getURLBasePath(baseURL) + relativeURL; } } /** * Returns the protocol of an absolute URL e.g. http, https etc. * * @param url absolute URL * @return protocol */ public static String getURLProtocol(String url) { int index = url.indexOf("://"); if (index != -1) { return url.substring(0, index); } return null; } /** * Returns the URL's host portion * * @param url absolute URL * @return the domain of the URL */ public static String getURLHost(String url) { int start = url.indexOf("://"); int end = url.indexOf('/', start + 3); if (end != -1) { return url.substring(start + 3, end); } else { return url.substring(start + 3); } } /** * Returns the URL's path * * @param url absolute URL * @return the path within the host */ public static String getURLPath(String url) { int start = url.indexOf('/', url.indexOf("://") + 3); if (start != -1) { return url.substring(start + 1); } return "/"; } /** * Returns the URL's base path, which is the same as the path only without an ending file e.g.: * http://domain.com/f/f.html would return as: /f/ * * @param url absolute URL * @return the path within the host */ public static String getURLBasePath(String url) { int start = url.indexOf('/', url.indexOf("://") + 3); int end = url.lastIndexOf('/'); if (start != -1 && end > start) { return url.substring(start, end + 1); } return "/"; } /** * Writes a string with a null flag, this allows a String which may be null * * @param s the string to write * @param d the destination output stream * @throws java.io.IOException */ public static void writeUTF(String s, DataOutputStream d) throws IOException { if(s == null) { d.writeBoolean(false); return; } d.writeBoolean(true); d.writeUTF(s); } /** * Reads a UTF string that may be null previously written by writeUTF * * @param d the stream * @return a string or null * @throws java.io.IOException */ public static String readUTF(DataInputStream d) throws IOException { if(d.readBoolean()) { return d.readUTF(); } return null; } /** * The read fully method from data input stream is very useful for all types of * streams... * * @param b the buffer into which the data is read. * @exception IOException the stream has been closed and the contained * input stream does not support reading after close, or * another I/O error occurs. */ public static void readFully(InputStream i, byte b[]) throws IOException { readFully(i, b, 0, b.length); } /** * The read fully method from data input stream is very useful for all types of * streams... * * @param b the buffer into which the data is read. * @param off the start offset of the data. * @param len the number of bytes to read. * @exception IOException the stream has been closed and the contained * input stream does not support reading after close, or * another I/O error occurs. */ public static final void readFully(InputStream i, byte b[], int off, int len) throws IOException { if (len < 0) { throw new IndexOutOfBoundsException(); } int n = 0; while (n < len) { int count = i.read(b, off + n, len - n); if (count < 0) { throw new EOFException(); } n += count; } } /** * Reads until the array is full or until the stream ends * * @param b the buffer into which the data is read. * @return the amount read * @exception IOException the stream has been closed and the contained * input stream does not support reading after close, or * another I/O error occurs. */ public static int readAll(InputStream i, byte b[]) throws IOException { int len = b.length; int n = 0; while (n < len) { int count = i.read(b, n, len - n); if (count < 0) { return n; } n += count; } return n; } /** * Provides a utility method breaks a given String to array of String according * to the given separator * @param original the String to break * @param separator the pattern to look in the original String * @return array of Strings from the original String */ public static String[] split(String original, String separator) { Vector nodes = new Vector(); int index = original.indexOf(separator); while (index >= 0) { nodes.addElement(original.substring(0, index)); original = original.substring(index + separator.length()); index = original.indexOf(separator); } nodes.addElement(original); String [] ret = new String[nodes.size()]; for (int i = 0; i < nodes.size(); i++) { ret[i] = (String) nodes.elementAt(i); } return ret; } /** * Invoked internally from Display, this method is for internal use only * * @param impl implementation instance */ public static void setImplementation(CodenameOneImplementation impl) { implInstance = impl; } static CodenameOneImplementation getImplementation() { return implInstance; } /** * Merges arrays into one larger array */ public static void mergeArrays(Object[] arr1, Object[] arr2, Object[] destinationArray) { System.arraycopy(arr1, 0, destinationArray, 0, arr1.length); System.arraycopy(arr2, 0, destinationArray, arr1.length, arr2.length); } /** * Removes the object at the source array offset and copies all other objects to the destination array * @param sourceArray the source array * @param destinationArray the resulting array which should be of the length sourceArray.length - 1 * @param o the object to remove from the array */ public static void removeObjectAtOffset(Object[] sourceArray, Object[] destinationArray, Object o) { int off = indexOf(sourceArray, o); removeObjectAtOffset(sourceArray, destinationArray, off); } /** * Removes the object at the source array offset and copies all other objects to the destination array * @param sourceArray the source array * @param destinationArray the resulting array which should be of the length sourceArray.length - 1 * @param offset the offset of the array */ public static void removeObjectAtOffset(Object[] sourceArray, Object[] destinationArray, int offset) { System.arraycopy(sourceArray, 0, destinationArray, 0, offset); System.arraycopy(sourceArray, offset + 1, destinationArray, offset, sourceArray.length - offset - 1); } /** * Inserts the object at the destination array offset * @param sourceArray the source array * @param destinationArray the resulting array which should be of the length sourceArray.length + 1 * @param offset the offset of the array * @param o the object */ public static void insertObjectAtOffset(Object[] sourceArray, Object[] destinationArray, int offset, Object o) { if(offset == 0) { destinationArray[0] = o; System.arraycopy(sourceArray, 0, destinationArray, 1, sourceArray.length); } else { if(offset == sourceArray.length) { System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length); destinationArray[sourceArray.length] = o; } else { System.arraycopy(sourceArray, 0, destinationArray, 0, offset); destinationArray[offset] = o; System.arraycopy(sourceArray, offset, destinationArray, offset + 1, sourceArray.length - offset); } } } /** * Finds the object at the given offset while using the == operator and not the equals method call, it doesn't * rely on the ordering of the elements like the Arrays method. * @param arr the array * @param value the value to search * @return the offset or -1 */ public static int indexOf(Object[] arr, Object value) { int l = arr.length; for(int iter = 0 ; iter < l ; iter++) { if(arr[iter] == value) { return iter; } } return -1; } /** * Blocking method that will download the given URL to storage and return when the * operation completes * @param url the URL * @param fileName the storage file name * @param showProgress whether to block the UI until download completes/fails * @return true on success false on error */ public static boolean downloadUrlToStorage(String url, String fileName, boolean showProgress) { return downloadUrlTo(url, fileName, showProgress, false, true, null); } /** * Blocking method that will download the given URL to the file system storage and return when the * operation completes * @param url the URL * @param fileName the file name * @param showProgress whether to block the UI until download completes/fails * @return true on success false on error */ public static boolean downloadUrlToFile(String url, String fileName, boolean showProgress) { return downloadUrlTo(url, fileName, showProgress, false, false, null); } /** *

Non-blocking method that will download the given URL to storage in the background and return * immediately. This method can be used to fetch data dynamically and asynchronously e.g. in this code it is used * to fetch book covers for the {@link com.codename1.components.ImageViewer}:

* * * Image viewer with dynamic URL fetching model * @param url the URL * @param fileName the storage file name */ public static void downloadUrlToStorageInBackground(String url, String fileName) { downloadUrlTo(url, fileName, false, true, true, null); } /** * Non-blocking method that will download the given URL to file system storage in the background and return immediately * @param url the URL * @param fileName the file name */ public static void downloadUrlToFileSystemInBackground(String url, String fileName) { downloadUrlTo(url, fileName, false, true, false, null); } /** * Non-blocking method that will download the given URL to storage in the background and return immediately * @param url the URL * @param fileName the storage file name * @param onCompletion invoked when download completes */ public static void downloadUrlToStorageInBackground(String url, String fileName, ActionListener onCompletion) { downloadUrlTo(url, fileName, false, true, true, onCompletion); } /** * Non-blocking method that will download the given URL to file system storage in the background and return immediately * @param url the URL * @param fileName the file name * @param onCompletion invoked when download completes */ public static void downloadUrlToFileSystemInBackground(String url, String fileName, ActionListener onCompletion) { downloadUrlTo(url, fileName, false, true, false, onCompletion); } /** * Downloads an image to the file system asynchronously. If the image is already downloaded it will just load it directly from * the file system. * @param url The URL to download the image from. * @param fileName The the path to the file where the image should be downloaded. If this file already exists, it will simply load this file and skip the * network request altogether. * @param onSuccess Callback called on success. * @param onFail Callback called if we fail to load the image. * @since 3.4 * @see ConnectionRequest#downloadImageToFileSystem(java.lang.String, com.codename1.util.SuccessCallback, com.codename1.util.FailureCallback) */ public static void downloadImageToFileSystem(String url, String fileName, SuccessCallback onSuccess, FailureCallback onFail) { implInstance.downloadImageToFileSystem(url, fileName, onSuccess, onFail); } /** * Downloads an image to the file system asynchronously. If the image is already downloaded it will just load it directly from * the file system. * @param url The URL to download the image from. * @param fileName The the path to the file where the image should be downloaded. If this file already exists, it will simply load this file and skip the * network request altogether. * @since 7.0 * @see ConnectionRequest#downloadImageToFileSystem(java.lang.String, com.codename1.util.SuccessCallback, com.codename1.util.FailureCallback) */ public static AsyncResource downloadImageToFileSystem(String url, String fileName) { final AsyncResource out = new AsyncResource(); downloadImageToFileSystem(url, fileName, new SuccessCallback() { @Override public void onSucess(Image value) { out.complete(value); } }, new FailureCallback() { @Override public void onError(Object sender, Throwable err, int errorCode, String errorMessage) { out.error(err); } } ); return out; } /** * Downloads an image to the file system asynchronously. If the image is already downloaded it will just load it directly from * the file system. * @param url The URL to download the image from. * @param fileName The the path to the file where the image should be downloaded. If this file already exists, it will simply load this file and skip the * network request altogether. * @param onSuccess Callback called on success. * @since 3.4 * @see ConnectionRequest#downloadImageToFileSystem(java.lang.String, com.codename1.util.SuccessCallback) */ public static void downloadImageToFileSystem(String url, String fileName, SuccessCallback onSuccess) { downloadImageToFileSystem(url, fileName, onSuccess, new CallbackAdapter()); } /** * Downloads an image to storage asynchronously. If the image is already downloaded it will just load it directly from * storage. * @param url The URL to download the image from. * @param fileName The the storage file to save the image to. If this file already exists, it will simply load this file and skip the * network request altogether. * @param onSuccess Callback called on success. * @param onFail Callback called if we fail to load the image. * @since 3.4 * @see ConnectionRequest#downloadImageToStorage(java.lang.String, com.codename1.util.SuccessCallback, com.codename1.util.FailureCallback) */ public static void downloadImageToStorage(String url, String fileName, SuccessCallback onSuccess, FailureCallback onFail) { implInstance.downloadImageToStorage(url, fileName, onSuccess, onFail); } /** * Downloads an image to storage asynchronously. If the image is already downloaded it will just load it directly from * storage. * @param url The URL to download the image from. * @param fileName The the storage file to save the image to. If this file already exists, it will simply load this file and skip the * network request altogether. * @since 7.0 * @see ConnectionRequest#downloadImageToStorage(java.lang.String, com.codename1.util.SuccessCallback, com.codename1.util.FailureCallback) */ public static AsyncResource downloadImageToStorage(String url, String fileName) { final AsyncResource out = new AsyncResource(); downloadImageToStorage(url, fileName, new SuccessCallback() { @Override public void onSucess(Image value) { out.complete(value); } }, new FailureCallback() { @Override public void onError(Object sender, Throwable err, int errorCode, String errorMessage) { out.error(err); } } ); return out; } /** * Downloads an image to the cache asynchronously. * @param url The URL to download. * @param onSuccess Callback to run on successful completion. * @param onFail Callback to run if download fails. */ public static void downloadImageToCache(String url, SuccessCallback onSuccess, FailureCallback onFail) { implInstance.downloadImageToCache(url, onSuccess, onFail); } /** * Downloads an image to the cache asynchronously. * @param url The URL of the image to download. * @return AsyncResource to wrap the Image. * @since 7.0 */ public static AsyncResource downloadImageToCache(String url) { final AsyncResource out = new AsyncResource(); downloadImageToCache(url, new SuccessCallback() { @Override public void onSucess(Image value) { out.complete(value); } }, new FailureCallback() { @Override public void onError(Object sender, Throwable err, int errorCode, String errorMessage) { out.error(err); } } ); return out; } /** * Downloads an image to storage asynchronously. If the image is already downloaded it will just load it directly from * storage. * @param url The URL to download the image from. * @param fileName The the storage file to save the image to. If this file already exists, it will simply load this file and skip the * network request altogether. * @param onSuccess Callback called on success. * @since 3.4 * @see ConnectionRequest#downloadImageToStorage(java.lang.String, com.codename1.util.SuccessCallback) */ public static void downloadImageToStorage(String url, String fileName, SuccessCallback onSuccess) { downloadImageToStorage(url, fileName, onSuccess, new CallbackAdapter()); } private static boolean downloadUrlTo(String url, String fileName, boolean showProgress, boolean background, boolean storage, ActionListener callback) { ConnectionRequest cr = new ConnectionRequest(); cr.setPost(false); cr.setFailSilently(true); cr.setReadResponseForErrors(false); cr.setDuplicateSupported(true); cr.setUrl(url); if(callback != null) { cr.addResponseListener(callback); } if(storage) { cr.setDestinationStorage(fileName); } else { cr.setDestinationFile(fileName); } if(background) { NetworkManager.getInstance().addToQueue(cr); return true; } if(showProgress) { InfiniteProgress ip = new InfiniteProgress(); Dialog d = ip.showInifiniteBlocking(); NetworkManager.getInstance().addToQueueAndWait(cr); d.dispose(); } else { NetworkManager.getInstance().addToQueueAndWait(cr); } if(cr.getContentLength() > 0) { // verify the resulting file has the same size as the content length if(storage) { if(Storage.getInstance().entrySize(fileName) < cr.getContentLength()) { return false; } } else { if(FileSystemStorage.getInstance().getLength(fileName) < cr.getContentLength()) { return false; } } } int rc = cr.getResponseCode(); return rc == 200 || rc == 201; } /** * Shorthand method for Thread sleep that doesn't throw the stupid interrupted checked exception * @param t the time */ public static void sleep(int t) { try { Thread.sleep(t); } catch(InterruptedException e) { } } /** * Shorthand method wait method that doesn't throw the stupid interrupted checked exception, it also * includes the synchronized block to further reduce code clutter * @param o the object to wait on * @param t the time */ public static void wait(Object o, int t) { synchronized(o) { try { o.wait(t); } catch(InterruptedException e) { } } } /** * Shorthand method wait method that doesn't throw the stupid interrupted checked exception, it also * includes the synchronized block to further reduce code clutter * @param o the object to wait on */ public static void wait(Object o) { synchronized(o) { try { o.wait(); } catch(InterruptedException e) { } } } /** * Returns true or false based on a "soft" object * @param val a boolean value as a Boolean object, String or number * @return true or false */ public static boolean toBooleanValue(Object val) { if(val == null) { return false; } if(val instanceof Boolean) { return ((Boolean)val).booleanValue(); } if(val instanceof String) { String sl = ((String)val).toLowerCase(); return sl.startsWith("t") || sl.equals("1"); } return toIntValue(val) != 0; } /** * Returns the number object as an int * @param number this can be a String or any number type * @return an int value or an exception */ public static int toIntValue(Object number) { if(number == null) { return 0; } // we should convert this to use Number if(number instanceof Integer) { return ((Integer)number).intValue(); } if(number instanceof String) { String n = (String)number; if(n.length() == 0 || n.equals(" ")) { return 0; } return Integer.parseInt(n); } if(number instanceof Double) { return ((Double)number).intValue(); } if(number instanceof Float) { return ((Float)number).intValue(); } if(number instanceof Long) { return ((Long)number).intValue(); } /*if(number instanceof Short) { return ((Short)number).intValue(); } if(number instanceof Byte) { return ((Byte)number).intValue(); }*/ if(number instanceof Boolean) { Boolean b = (Boolean)number; if(b.booleanValue()) { return 1; } return 0; } throw new IllegalArgumentException("Not a number: " + number); } /** * Returns the number object as a long * @param number this can be a String or any number type * @return a long value or an exception */ public static long toLongValue(Object number) { // we should convert this to use Number if(number instanceof Long) { return ((Long)number).longValue(); } if(number instanceof Integer) { return ((Integer)number).longValue(); } if(number instanceof String) { return Long.parseLong((String)number); } if(number instanceof Double) { return ((Double)number).longValue(); } if(number instanceof Float) { return ((Float)number).longValue(); } if(number instanceof Date) { return ((Date)number).getTime(); } /*if(number instanceof Short) { return ((Short)number).longValue(); } if(number instanceof Byte) { return ((Byte)number).longValue(); }*/ if(number instanceof Boolean) { Boolean b = (Boolean)number; if(b.booleanValue()) { return 1; } return 0; } throw new IllegalArgumentException("Not a number: " + number); } /** * Returns the number object as a float * @param number this can be a String or any number type * @return a float value or an exception */ public static float toFloatValue(Object number) { // we should convert this to use Number if(number instanceof Float) { return ((Float)number).floatValue(); } if(number instanceof Long) { return ((Long)number).floatValue(); } if(number instanceof Integer) { return ((Integer)number).floatValue(); } if(number instanceof String) { return Float.parseFloat((String)number); } if(number instanceof Double) { return ((Double)number).floatValue(); } /*if(number instanceof Short) { return ((Short)number).floatValue(); } if(number instanceof Byte) { return ((Byte)number).floatValue(); }*/ if(number instanceof Boolean) { Boolean b = (Boolean)number; if(b.booleanValue()) { return 1; } return 0; } throw new IllegalArgumentException("Not a number: " + number); } /** * Returns the number object as a double * @param number this can be a String or any number type * @return a double value or an exception */ public static double toDoubleValue(Object number) { // we should convert this to use Number if(number instanceof Double) { return ((Double)number).doubleValue(); } if(number instanceof Float) { return ((Float)number).doubleValue(); } if(number instanceof Long) { return ((Long)number).doubleValue(); } if(number instanceof Integer) { return ((Integer)number).doubleValue(); } if(number instanceof String) { return Double.parseDouble((String)number); } /*if(number instanceof Short) { return ((Short)number).doubleValue(); } if(number instanceof Byte) { return ((Byte)number).doubleValue(); }*/ if(number instanceof Boolean) { Boolean b = (Boolean)number; if(b.booleanValue()) { return 1; } return 0; } throw new IllegalArgumentException("Not a number: " + number); } private static SimpleDateFormat dateFormatter; /** * Sets a custom formatter to use when toDateValue is invoked * @param formatter the formatter to use */ public static void setDateFormatter(SimpleDateFormat formatter) { dateFormatter = formatter; } /** * Tries to convert an arbitrary object to a date * @param o an object that can be a string, number or date * @return a Date object */ public static Date toDateValue(Object o) { if(o == null) { return null; } if(o instanceof Date) { return (Date)o; } if(o instanceof String) { if(dateFormatter != null) { try { return dateFormatter.parse((String)o); } catch(ParseException e) { // falls back to the default formatting Log.e(e); } } try { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse((String)o); } catch(ParseException e) { throw new IllegalArgumentException("Not a supported date, we use this format 'yyyy-MM-dd'T'HH:mm:ss.SSS': " + o); } } return new Date(toLongValue(o)); } /** * Encodes a string in a way that makes it harder to read it "as is" this makes it possible for Strings to be * "encoded" within the app and thus harder to discover by a casual search. * * @param s the string to decode * @return the decoded string */ public static String xorDecode(String s) { try { byte[] dat = Base64.decode(s.getBytes("UTF-8")); for(int iter = 0 ; iter < dat.length ; iter++) { dat[iter] = (byte)(dat[iter] ^ (iter % 254 + 1)); } return new String(dat, "UTF-8"); } catch(UnsupportedEncodingException err) { // will never happen damn stupid exception err.printStackTrace(); return null; } } /** * The inverse method of xorDecode, this is normally unnecessary and is here mostly for completeness * * @param s a regular string * @return a String that can be used in the xorDecode method */ public static String xorEncode(String s) { try { byte[] dat = s.getBytes("UTF-8"); for(int iter = 0 ; iter < dat.length ; iter++) { dat[iter] = (byte)(dat[iter] ^ (iter % 254 + 1)); } return Base64.encodeNoNewline(dat); } catch(UnsupportedEncodingException err) { // will never happen damn stupid exception err.printStackTrace(); return null; } } /** * Tries to determine the mime type of a file based on its first * bytes.Direct inspection of the bytes to determine the content type is * often more accurate than believing the content type claimed by the * http server or by the file extension. * * @param sourceFile, it automatically choose Storage API or * FileSystemStorage API * @return the detected mime type, or "application/octet-stream" if the type * is not detected * @throws IOException */ public static String guessMimeType(String sourceFile) throws IOException { InputStream inputStream; if (sourceFile.indexOf('/') > -1) { inputStream = FileSystemStorage.getInstance().openInputStream(sourceFile); } else { // Storage is a flat file system inputStream = Storage.getInstance().createInputStream(sourceFile); } return guessMimeType(inputStream); } /** * Tries to determine the mime type of an InputStream based on its first * bytes.Direct inspection of the bytes to determine the content type is * often more accurate than believing the content type claimed by the * http server or by the file extension. * * @param in * @return the detected mime type, or "application/octet-stream" if the type * is not detected * @throws IOException */ public static String guessMimeType(InputStream in) throws IOException { byte[] header = new byte[11]; in.read(header, 0, 11); return guessMimeType(header); } /** * Tries to determine the mime type of a byte array based on its first * bytes.Direct inspection of the bytes to determine the content type is * often more accurate than believing the content type claimed by the * http server or by the file extension. * * @param data * @return the detected mime type, or "application/octet-stream" if the type * is not detected */ public static String guessMimeType(byte[] data) { // I took the most of header codes from: https://github.com/Servoy/servoy-client/blob/e7f5bce3c3dc0f0eb1cd240fce48c75143a25432/servoy_shared/src/com/servoy/j2db/util/MimeTypes.java // For further reference, the header codes used by OpenJDK8 are here: https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/java/net/URLConnection.java // This method can be improved according to the needs. if (data == null || data.length == 0) { throw new IllegalArgumentException("guessMimeType(byte[] data) -> data cannot be empty or null"); } // If you change the number of byte, REMEMBER to change that number also in the method guessMimeType(InputStream in) byte[] header = new byte[11]; System.arraycopy(data, 0, header, 0, Math.min(data.length, header.length)); int c1 = header[0] & 0xff; int c2 = header[1] & 0xff; int c3 = header[2] & 0xff; int c4 = header[3] & 0xff; int c5 = header[4] & 0xff; int c6 = header[5] & 0xff; int c7 = header[6] & 0xff; int c8 = header[7] & 0xff; int c9 = header[8] & 0xff; int c10 = header[9] & 0xff; int c11 = header[10] & 0xff; if (c1 == 0xCA && c2 == 0xFE && c3 == 0xBA && c4 == 0xBE) { return "application/java-vm"; } if (c1 == 0xD0 && c2 == 0xCF && c3 == 0x11 && c4 == 0xE0 && c5 == 0xA1 && c6 == 0xB1 && c7 == 0x1A && c8 == 0xE1) { return "application/msword"; } if (c1 == 0x25 && c2 == 0x50 && c3 == 0x44 && c4 == 0x46 && c5 == 0x2d && c6 == 0x31 && c7 == 0x2e) { return "application/pdf"; } if (c1 == 0x38 && c2 == 0x42 && c3 == 0x50 && c4 == 0x53 && c5 == 0x00 && c6 == 0x01) { return "image/photoshop"; } if (c1 == 0x25 && c2 == 0x21 && c3 == 0x50 && c4 == 0x53) { return "application/postscript"; } if (c1 == 0xff && c2 == 0xfb && c3 == 0x30) { return "audio/mp3"; } if (c1 == 0x49 && c2 == 0x44 && c3 == 0x33) { return "audio/mp3"; } if (c1 == 0xAC && c2 == 0xED) { // next two bytes are version number, currently 0x00 0x05 return "application/x-java-serialized-object"; } if (c1 == '<') { if (c2 == '!' || ((c2 == 'h' && (c3 == 't' && c4 == 'm' && c5 == 'l' || c3 == 'e' && c4 == 'a' && c5 == 'd') || (c2 == 'b' && c3 == 'o' && c4 == 'd' && c5 == 'y'))) || ((c2 == 'H' && (c3 == 'T' && c4 == 'M' && c5 == 'L' || c3 == 'E' && c4 == 'A' && c5 == 'D') || (c2 == 'B' && c3 == 'O' && c4 == 'D' && c5 == 'Y')))) { return "text/html"; } if (c2 == '?' && c3 == 'x' && c4 == 'm' && c5 == 'l' && c6 == ' ') { return "application/xml"; } } // big and little endian UTF-16 encodings, with byte order mark if (c1 == 0xfe && c2 == 0xff) { if (c3 == 0 && c4 == '<' && c5 == 0 && c6 == '?' && c7 == 0 && c8 == 'x') { return "application/xml"; } } if (c1 == 0xff && c2 == 0xfe) { if (c3 == '<' && c4 == 0 && c5 == '?' && c6 == 0 && c7 == 'x' && c8 == 0) { return "application/xml"; } } if (c1 == 'B' && c2 == 'M') { return "image/bmp"; } if (c1 == 0x49 && c2 == 0x49 && c3 == 0x2a && c4 == 0x00) { return "image/tiff"; } if (c1 == 0x4D && c2 == 0x4D && c3 == 0x00 && c4 == 0x2a) { return "image/tiff"; } if (c1 == 'G' && c2 == 'I' && c3 == 'F' && c4 == '8') { return "image/gif"; } if (c1 == '#' && c2 == 'd' && c3 == 'e' && c4 == 'f') { return "image/x-bitmap"; } if (c1 == '!' && c2 == ' ' && c3 == 'X' && c4 == 'P' && c5 == 'M' && c6 == '2') { return "image/x-pixmap"; } if (c1 == 137 && c2 == 80 && c3 == 78 && c4 == 71 && c5 == 13 && c6 == 10 && c7 == 26 && c8 == 10) { return "image/png"; } if (c1 == 0xFF && c2 == 0xD8 && c3 == 0xFF) { if (c4 == 0xE0) { return "image/jpeg"; } /** * File format used by digital cameras to store images. Exif Format * can be read by any application supporting JPEG. Exif Spec can be * found at: * http://www.pima.net/standards/it10/PIMA15740/Exif_2-1.PDF */ if ((c4 == 0xE1) && (c7 == 'E' && c8 == 'x' && c9 == 'i' && c10 == 'f' && c11 == 0)) { return "image/jpeg"; } if (c4 == 0xEE) { return "image/jpg"; } } /** * According to * http://www.opendesign.com/files/guestdownloads/OpenDesign_Specification_for_.dwg_files.pdf * first 6 bytes are of type "AC1018" (for example) and the next 5 bytes * are 0x00. */ if ((c1 == 0x41 && c2 == 0x43) && (c7 == 0x00 && c8 == 0x00 && c9 == 0x00 && c10 == 0x00 && c11 == 0x00)) { return "application/acad"; } if (c1 == 0x2E && c2 == 0x73 && c3 == 0x6E && c4 == 0x64) { return "audio/basic"; // .au // format, // big // endian } if (c1 == 0x64 && c2 == 0x6E && c3 == 0x73 && c4 == 0x2E) { return "audio/basic"; // .au // format, // little // endian } if (c1 == 'R' && c2 == 'I' && c3 == 'F' && c4 == 'F') { /* * I don't know if this is official but evidence suggests that .wav files start with "RIFF" - brown */ return "audio/x-wav"; } if (c1 == 'P' && c2 == 'K') { return "application/zip"; } return "application/octet-stream"; // unknown file type } /** * Returns -1 if the content length is unknown, a value greater than 0 if * the Content-Length is known. * * @param url * @return Content-Length if known */ public static long getFileSizeWithoutDownload(final String url) { return getFileSizeWithoutDownload(url, false); } /** * Returns -2 if the server doesn't accept partial downloads (and if * checkPartialDownloadSupport is true), -1 if the content length is unknow, * a value greater than 0 if the Content-Length is known. * * @param url * @param checkPartialDownloadSupport if true returns -2 if the server * doesn't accept partial downloads. * @return Content-Length if known */ public static long getFileSizeWithoutDownload(final String url, final boolean checkPartialDownloadSupport) { // documentation about the headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests // code discussed here: https://stackoverflow.com/a/62130371 final Wrapper result = new Wrapper(0l); ConnectionRequest cr = new GZConnectionRequest() { @Override protected void readHeaders(Object connection) throws IOException { String acceptRanges = getHeader(connection, "Accept-Ranges"); if (checkPartialDownloadSupport && (acceptRanges == null || !acceptRanges.equals("bytes"))) { // Log.p("The partial downloads of " + url + " are not supported.", Log.WARNING); result.set(-2l); } else { String contentLength = getHeader(connection, "Content-Length"); if (contentLength != null) { result.set(Long.parseLong(contentLength)); } else { // Log.p("The Content-Length of " + url + " is unknown.", Log.WARNING); result.set(-1l); } } } }; cr.setUrl(url); cr.setHttpMethod("HEAD"); cr.setPost(false); NetworkManager.getInstance().addToQueueAndWait(cr); return result.get(); } /** *

* Safely download the given URL to the Storage or to the FileSystemStorage: * this method is resistant to network errors and capable of resume the * download as soon as network conditions allow and in a completely * transparent way for the user; note that in the global network error * handling, there must be an automatic *

.retry()
, as in the code example below.

*

* This method is useful if the server correctly returns Content-Length and * if it supports partial downloads: if not, it works like a normal * download.

*

* Pros: always allows you to complete downloads, even if very heavy (e.g. * 100MB), even if the connection is unstable (network errors) and even if * the app goes temporarily in the background (on some platforms the * download will continue in the background, on others it will be * temporarily suspended).

*

* Cons: since this method is based on splitting the download into small * parts (512kbytes is the default), this approach causes many GET requests * that slightly slow down the download and cause more traffic than normally * needed.

*

* Usage example:

* * * * @param url * @param fileName must be a valid Storage file name or FileSystemStorage * file path * @param percentageCallback invoked (in EDT) during the download to notify * the progress (from 0 to 100); it can be null if you are not interested in * monitoring the progress * @param filesavedCallback invoked (in EDT) only when the download is * finished; if null, no action is taken * @throws IOException */ public static void downloadUrlSafely(String url, final String fileName, final OnComplete percentageCallback, final OnComplete filesavedCallback) throws IOException { // Code discussion here: https://stackoverflow.com/a/62137379/1277576 String partialDownloadsDir = FileSystemStorage.getInstance().getAppHomePath() + FileSystemStorage.getInstance().getFileSystemSeparator() + "partialDownloads"; if (!FileSystemStorage.getInstance().isDirectory(partialDownloadsDir)) { FileSystemStorage.getInstance().mkdir(partialDownloadsDir); } final String uniqueId = url.hashCode() + "" + downloadUrlSafelyRandom.nextInt(); // do its best to be unique if there are parallel downloads final String partialDownloadPath = partialDownloadsDir + FileSystemStorage.getInstance().getFileSystemSeparator() + uniqueId; final boolean isStorage = fileName.indexOf("/") < 0; // as discussed here: https://stackoverflow.com/a/57984257 final long fileSize = getFileSizeWithoutDownload(url, true); // total expected download size, with a check partial download support final int splittingSize = 512 * 1024; // 512 kbyte, size of each small download final Wrapper downloadedTotalBytes = new Wrapper(0l); final OutputStream out; if (isStorage) { out = Storage.getInstance().createOutputStream(fileName); // leave it open to append partial downloads } else { out = FileSystemStorage.getInstance().openOutputStream(fileName); } final EasyThread mergeFilesThread = EasyThread.start("mergeFilesThread"); // Codename One thread that supports crash protection and similar Codename One features. final ConnectionRequest cr = new GZConnectionRequest(); cr.setUrl(url); cr.setPost(false); if (fileSize > splittingSize) { // Which byte should the download start from? cr.addRequestHeader("Range", "bytes=0-" + splittingSize); cr.setDestinationFile(partialDownloadPath); } else { Util.cleanup(out); if (isStorage) { cr.setDestinationStorage(fileName); } else { cr.setDestinationFile(fileName); } } cr.addResponseListener(new ActionListener() { @Override public void actionPerformed(NetworkEvent evt) { mergeFilesThread.run(new Runnable() { @Override public void run() { try { // We append the just saved partial download to the fileName, if it exists if (FileSystemStorage.getInstance().exists(partialDownloadPath)) { InputStream in = FileSystemStorage.getInstance().openInputStream(partialDownloadPath); Util.copyNoClose(in, out, 8192); Util.cleanup(in); // before deleting the file, we check and update how much data we have actually downloaded downloadedTotalBytes.set(downloadedTotalBytes.get() + FileSystemStorage.getInstance().getLength(partialDownloadPath)); FileSystemStorage.getInstance().delete(partialDownloadPath); } // Is the download finished? if (downloadedTotalBytes.get() > fileSize) { throw new IllegalStateException("More content has been downloaded than the file length, check the code."); } if (fileSize <= 0 || downloadedTotalBytes.get() == fileSize) { // yes, download finished Util.cleanup(out); if (filesavedCallback != null) { CN.callSerially(new Runnable() { @Override public void run() { filesavedCallback.completed(fileName); } }); } } else { // no, it's not finished, we repeat the request after updating the "Range" header cr.addRequestHeader("Range", "bytes=" + downloadedTotalBytes.get() + "-" + (Math.min(downloadedTotalBytes.get() + splittingSize, fileSize))); NetworkManager.getInstance().addToQueue(cr); } } catch (IOException ex) { Log.p("Error in appending splitted file to output file", Log.ERROR); Log.e(ex); Log.sendLogAsync(); } } }); } }); NetworkManager.getInstance().addToQueue(cr); NetworkManager.getInstance().addProgressListener(new ActionListener() { @Override public void actionPerformed(NetworkEvent evt) { if (cr == evt.getConnectionRequest() && fileSize > 0) { // the following casting to long is necessary when the file is bigger than 21MB, otherwise the result of the calculation is wrong if (percentageCallback != null) { CN.callSerially(new Runnable() { @Override public void run() { percentageCallback.completed((int) ((long) downloadedTotalBytes.get() * 100 / fileSize)); } }); } } } }); } /** *

* Creates a new UUID, that is a 128-bit number used to identify information * in computer systems. UUIDs aim to be unique for practical purposes.

* *

* This implementation uses the system clock and some device info as seeds * for random data, that are enough for practical usage. More specifically, * two instances of Random, instantiated with different seeds, are used. The * first seed corresponds to the timestamp in which the first object of the * static class UUID is created, the second seed is a number (long type) * that identifies the current installation of the app and is assumed to be * as different as possible from other installations of the app. A unique * identifier (long type) associated with the current app installation can * be specified by the developer via the Preference "CustomDeviceId__$" (as * in the following example) BEFORE the generation of the first UIID, or - * if it is not specified - it is obtained from an internal Codename One * implementation; in the worst case, if an identifier has not been * specified by the developer and Codename One is unable to distinguish the * current installation of the app from other installations, an internal * algorithm will be used that will generate a number based on some hardware * and software characteristics of the device: the number thus generated * will be the same on identical models of the same device and with the same * version of the operating system, but will vary between different models. * Even in the worst case scenario, the probability that two app * installations with identical device identifiers will generate the first * UIID in the same timestamp is very low.

* *

* As a tip, consider that any alphanumeric text string (corresponding for * example to a username) can be converted into a long type number, * considering this string as a number based on 36, provided it does not * exceed 12 characters. This suggestion is applied in the following * example.

* *

* Code example:

* * * @return a pseudo-random Universally Unique Identifier in its canonical * textual representation */ public static String getUUID() { return new Util.UUID().toString(); } /** * Creates a custom UUID, from the given two long values. * * @param time the upper 64 bits * @param clockSeqAndNode the lower 64 bits * * @return a Universally Unique Identifier in its canonical textual * representation */ public static String getUUID(long time, long clockSeqAndNode) { return new Util.UUID(time, clockSeqAndNode).toString(); } /** * This class represents an UUID according to the DCE Universal Token * Identifier specification. *

* All you need to know: *

     * UUID u = new UUID().toString();
     * 
*/ static class UUID { /* * UUID - an implementation of the UUID specification for Codename One * by Francesco Galgani * * You can use this class as you want (public-domain license). */ private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static final Random randomTime = new Random(System.currentTimeMillis()); private static final Random randomClockSeqAndNode = new Random(getUniqueDeviceID()); private long time = 0l; private long clockSeqAndNode = 0l; /** * Constructor for UUID, it uses the system clock and some device info * as seeds for random data, that are enough for practical usage. */ public UUID() { this.time = randomTime.nextLong(); this.clockSeqAndNode = randomClockSeqAndNode.nextLong(); } /** * Constructs a UUID from two long values. * * @param time the upper 64 bits * @param clockSeqAndNode the lower 64 bits */ public UUID(long time, long clockSeqAndNode) { this.time = time; this.clockSeqAndNode = clockSeqAndNode; } /** * Returns this UUID as a String. * * @return a String, never null */ @Override public final String toString() { return toCanonicalForm(); } private String toCanonicalForm() { String out = ""; out = append(out, (int) (time >> 32)) + '-' + append(out, (short) (time >> 16)) + '-' + append(out, (short) time) + '-' + append(out, (short) (clockSeqAndNode >> 48)) + '-' + append(out, clockSeqAndNode, 12); return out; } private static String append(String a, short in) { return append(a, (long) in, 4); } private static String append(String a, int in) { return append(a, (long) in, 8); } private static String append(String a, long in, int length) { int lim = (length << 2) - 4; while (lim >= 0) { a += (DIGITS[(byte) (in >> lim) & 0x0f]); lim -= 4; } return a; } private static long getUniqueDeviceID() { long id = Preferences.get("CustomDeviceId__$", (long) -1); if (id == -1) { id = Preferences.get("DeviceId__$", (long) -1); } if (id == -1) { id = generateLongFromDeviceInfo(); } return id; } /** * Generates a long number using some device info: the same type of * device (a specific model of a specific brand, with the same OS * version) will always produce the same number, while different devices * will most likely produce different numbers. * * @return */ private static long generateLongFromDeviceInfo() { long random = CN.getDeviceDensity() * CN.getDisplayHeight() * CN.getDisplayWidth() * CN.convertToPixels(10) * Long.parseLong(sanitizeString(CN.getPlatformName()), 36) * Long.parseLong(sanitizeString(CN.getProperty("User-Agent", "1")), 36) * Long.parseLong(sanitizeString(CN.getProperty("OSVer", "1")), 36); return random; } /** * Removes all non-alphanumeric characters from a string and returns the * first 10 characters. * * @param input * @return */ private static String sanitizeString(String input) { String result = ""; for (char myChar : input.toCharArray()) { if ((myChar >= '0' && myChar <= '9') || (myChar >= 'a' && myChar <= 'z') || (myChar >= 'A' && myChar <= 'Z')) { result += myChar; } } return result.substring(0, Math.min(10, result.length())).toUpperCase(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy