Package org.apache.openejb.util.io

Source Code of org.apache.openejb.util.io.ObjectOutputStream

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.util.io;

import java.io.Externalizable;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.NotSerializableException;
import java.io.ObjectOutput;
import java.io.ObjectStreamConstants;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.openejb.util.ArrayStack;

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {

//    private Handles handles;
    private OutputStream out;
    private ArrayStack classDescStack;
    private byte buf[] = new byte[5000];
    private int count;

    public ObjectOutputStream(OutputStream out) throws IOException {
        this.out = out;

        classDescStack = new ArrayStack();
    }

    public void reset() throws IOException {
        resetStream();
        count = 0;
    }

    public void serializeObject(Object obj, OutputStream out) throws NotSerializableException, IOException {
        this.out = out;
        serializeObject(obj);
    }

    public void serializeObject(Object obj) throws NotSerializableException, IOException {

        if (!Serializable.class.isAssignableFrom(obj.getClass()) && !Externalizable.class.isAssignableFrom(obj.getClass())) {
            throw new NotSerializableException(obj.getClass().getName());
        }

        reset();

        writeShort(STREAM_MAGIC);
        writeShort(STREAM_VERSION);
        writeObject(obj);

    }

    public void writeObject(Object obj) throws IOException {
        try {
            if (obj == null) {
                write(TC_NULL);
                return;
            }
            Class clazz = obj.getClass();
            ClassDescriptor classDesc = null;

            if (clazz == ClassDescriptor.class) classDesc = (ClassDescriptor) obj;
            else
                classDesc = ClassDescriptor.lookupInternal(clazz);

            if (classDesc == null) {
                write(TC_NULL);
                return;
            }

            int tmpInt = findWireOffset(obj);
            if (tmpInt >= 0) {
                write(TC_REFERENCE);
                tmpInt += baseWireHandle;

                write((tmpInt >>> 24) & 0xFF);
                write((tmpInt >>> 16) & 0xFF);
                write((tmpInt >>> 8) & 0xFF);
                write((tmpInt >>> 0) & 0xFF);

                return;
            }

            if (obj instanceof Class) {
                write(TC_CLASS);
                write(TC_CLASSDESC);
                writeUTF(classDesc.getName());
                long value = classDesc.getSerialVersionUID();

                write((int) (value >>> 56) & 0xFF);
                write((int) (value >>> 48) & 0xFF);
                write((int) (value >>> 40) & 0xFF);
                write((int) (value >>> 32) & 0xFF);
                write((int) (value >>> 24) & 0xFF);
                write((int) (value >>> 16) & 0xFF);
                write((int) (value >>> 8) & 0xFF);
                write((int) (value >>> 0) & 0xFF);

                assignWireOffset(classDesc);
                classDesc.writeClassInfo(this);
                write(TC_ENDBLOCKDATA);
                writeObject(classDesc.getSuperclass());
                assignWireOffset(clazz);
                return;
            }

            if (obj instanceof ClassDescriptor) {
                write(TC_CLASSDESC);
                writeUTF(classDesc.getName());
                long value = classDesc.getSerialVersionUID();

                write((int) (value >>> 56) & 0xFF);
                write((int) (value >>> 48) & 0xFF);
                write((int) (value >>> 40) & 0xFF);
                write((int) (value >>> 32) & 0xFF);
                write((int) (value >>> 24) & 0xFF);
                write((int) (value >>> 16) & 0xFF);
                write((int) (value >>> 8) & 0xFF);
                write((int) (value >>> 0) & 0xFF);

                assignWireOffset(classDesc);
                write(classDesc.flags);
                tmpInt = classDesc.fields.length;
                write((tmpInt >>> 8) & 0xFF);
                write((tmpInt >>> 0) & 0xFF);
                FieldDescriptor field;
                for (int i = 0; i < classDesc.fields.length; i++) {
                    field = classDesc.fields[i];
                    write((int) field.typeCode);
                    writeUTF(field.name);
                    if (!field.type.isPrimitive()) writeObject(field.typeString);
                }
                write(TC_ENDBLOCKDATA);
                writeObject(classDesc.getSuperclass());
                return;
            }
            if (obj instanceof String) {
                write(TC_STRING);
                String s = ((String) obj).intern();
                assignWireOffset(s);
                writeUTF(s);
                return;
            }
            if (clazz.isArray()) {
                write(TC_ARRAY);
                writeObject(classDesc);
                assignWireOffset(obj);

                Class type = clazz.getComponentType();
                if (type.isPrimitive()) {
                    if (type == Integer.TYPE) {
                        int[] array = (int[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        int value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = array[i];

                            write((value >>> 24) & 0xFF);
                            write((value >>> 16) & 0xFF);
                            write((value >>> 8) & 0xFF);
                            write((value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Byte.TYPE) {
                        byte[] array = (byte[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        write(array, 0, tmpInt);
                        return;
                    } else if (type == Long.TYPE) {
                        long[] array = (long[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        long value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = array[i];

                            write((int) (value >>> 56) & 0xFF);
                            write((int) (value >>> 48) & 0xFF);
                            write((int) (value >>> 40) & 0xFF);
                            write((int) (value >>> 32) & 0xFF);
                            write((int) (value >>> 24) & 0xFF);
                            write((int) (value >>> 16) & 0xFF);
                            write((int) (value >>> 8) & 0xFF);
                            write((int) (value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Float.TYPE) {
                        float[] array = (float[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        int value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = Float.floatToIntBits(array[i]);

                            write((value >>> 24) & 0xFF);
                            write((value >>> 16) & 0xFF);
                            write((value >>> 8) & 0xFF);
                            write((value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Double.TYPE) {
                        double[] array = (double[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        long value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = Double.doubleToLongBits(array[i]);

                            write((int) (value >>> 56) & 0xFF);
                            write((int) (value >>> 48) & 0xFF);
                            write((int) (value >>> 40) & 0xFF);
                            write((int) (value >>> 32) & 0xFF);
                            write((int) (value >>> 24) & 0xFF);
                            write((int) (value >>> 16) & 0xFF);
                            write((int) (value >>> 8) & 0xFF);
                            write((int) (value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Short.TYPE) {
                        short[] array = (short[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        short value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = array[i];

                            write((value >>> 8) & 0xFF);
                            write((value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Character.TYPE) {
                        char[] array = (char[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        char value;
                        for (int i = 0; i < tmpInt; i++) {
                            value = array[i];

                            write((value >>> 8) & 0xFF);
                            write((value >>> 0) & 0xFF);

                        }
                        return;
                    } else if (type == Boolean.TYPE) {
                        boolean[] array = (boolean[]) obj;
                        tmpInt = array.length;

                        write((tmpInt >>> 24) & 0xFF);
                        write((tmpInt >>> 16) & 0xFF);
                        write((tmpInt >>> 8) & 0xFF);
                        write((tmpInt >>> 0) & 0xFF);

                        for (int i = 0; i < tmpInt; i++) {
                            write(array[i] ? 1 : 0);
                        }
                        return;
                    } else {
                        throw new InvalidClassException(clazz.getName());
                    }
                } else {
                    Object[] array = (Object[]) obj;
                    int length = array.length;

                    write((length >>> 24) & 0xFF);
                    write((length >>> 16) & 0xFF);
                    write((length >>> 8) & 0xFF);
                    write((length >>> 0) & 0xFF);

                    for (int i = 0; i < length; i++)
                        writeObject(array[i]);
                }
                return;
            }
            write(TC_OBJECT);
            writeObject(classDesc);
            assignWireOffset(obj);

            if (classDesc.isExternalizable()) {
                writeExternal((Externalizable) obj);
                return;
            }

            int stackMark = classDescStack.size();
            try {

                ClassDescriptor superClassDesc;
                while ((superClassDesc = classDesc.getSuperclass()) != null) {
                    classDescStack.push(classDesc);
                    classDesc = superClassDesc;
                }

                do {
                    if (classDesc.hasWriteObjectMethod()) {
                        /* DMB:  NOT COMPLETE - Should start writing in block data format
                         * and state the size of the data to come.
                         */

                        /* DMB:  NOT COMPLETE - Should Invoke the writeObject
                         * mehtod on the object.
                         * Invoking the write object method requires a
                         * sublcass of java.io.ObjectOutputStream to be
                         * passed in.  This implementation is not a subclass
                         * of java.io.ObjectOutputStream.
                         */

                        /* DMB:  NOT COMPLETE - Should stop writing in block data format.
                         * Denote the end of this mode by writing a terminator to the stream.
                         */

                    } else {
                        FieldDescriptor[] fields = classDesc.getFields();
                        Field field;
                        if (fields.length > 0) {
                            for (int i = 0; i < fields.length; i++) {
                                field = fields[i].getField();
                                if (field == null) throw new InvalidClassException(clazz.getName(), "Nonexistent field " + fields[i].getName());
                                try {
                                    switch (fields[i].getTypeCode()) {
                                        case 'B':
                                            write(field.getByte(obj));
                                            break;
                                        case 'C':
                                            char charvalue = field.getChar(obj);
                                            write((charvalue >>> 8) & 0xFF);
                                            write((charvalue >>> 0) & 0xFF);
                                            break;
                                        case 'I':
                                            int intvalue = field.getInt(obj);
                                            write((intvalue >>> 24) & 0xFF);
                                            write((intvalue >>> 16) & 0xFF);
                                            write((intvalue >>> 8) & 0xFF);
                                            write((intvalue >>> 0) & 0xFF);
                                            break;
                                        case 'Z':
                                            write((field.getBoolean(obj) ? 1 : 0));
                                            break;
                                        case 'J':
                                            long longvalue = field.getLong(obj);
                                            write((int) (longvalue >>> 56) & 0xFF);
                                            write((int) (longvalue >>> 48) & 0xFF);
                                            write((int) (longvalue >>> 40) & 0xFF);
                                            write((int) (longvalue >>> 32) & 0xFF);
                                            write((int) (longvalue >>> 24) & 0xFF);
                                            write((int) (longvalue >>> 16) & 0xFF);
                                            write((int) (longvalue >>> 8) & 0xFF);
                                            write((int) (longvalue >>> 0) & 0xFF);
                                            break;
                                        case 'F':
                                            int floatvalue = Float.floatToIntBits(field.getFloat(obj));
                                            write((floatvalue >>> 24) & 0xFF);
                                            write((floatvalue >>> 16) & 0xFF);
                                            write((floatvalue >>> 8) & 0xFF);
                                            write((floatvalue >>> 0) & 0xFF);
                                            break;
                                        case 'D':
                                            long doublevalue = Double.doubleToLongBits(field.getDouble(obj));
                                            write((int) (doublevalue >>> 56) & 0xFF);
                                            write((int) (doublevalue >>> 48) & 0xFF);
                                            write((int) (doublevalue >>> 40) & 0xFF);
                                            write((int) (doublevalue >>> 32) & 0xFF);
                                            write((int) (doublevalue >>> 24) & 0xFF);
                                            write((int) (doublevalue >>> 16) & 0xFF);
                                            write((int) (doublevalue >>> 8) & 0xFF);
                                            write((int) (doublevalue >>> 0) & 0xFF);
                                            break;
                                        case 'S':
                                            short shortvalue = field.getShort(obj);
                                            write((shortvalue >>> 8) & 0xFF);
                                            write((shortvalue >>> 0) & 0xFF);
                                            break;
                                        case '[':
                                        case 'L':
                                            writeObject(field.get(obj));
                                            break;
                                        default:
                                            throw new InvalidClassException(clazz.getName());
                                    }
                                } catch (IllegalAccessException e) {
                                    throw (InvalidClassException)new InvalidClassException(clazz.getName(), e.getMessage()).initCause(e);
                                } finally {
                                }
                            }
                        }
                    }
                } while (classDescStack.size() > stackMark && (classDesc = (ClassDescriptor) classDescStack.pop()) != null);

            } finally {
                /* If an error occcured, make sure we set the stack back
                 * the way it was before we started.
                 */

            }

        } finally {
        }
    }

    public void writeString(String s) throws IOException {
        writeObject(s);
    }

    private void writeExternal(Externalizable ext) throws IOException {
//      if (useDeprecatedExternalizableFormat) {
        if (false) {
            /* JDK 1.1 external data format.
             * Don't write in block data mode and no terminator tag.
             */
            /* This method accepts a java.io.OutputStream as a parameter */
            ext.writeExternal(this);
        } else {
            /* JDK 1.2 Externalizable data format writes in block data mode
             * and terminates externalizable data with TAG_ENDBLOCKDATA.
             */
            /* DMB:  NOT COMPLETE - Should start writing in block data format.
             * This states the size of the data to come
             */

            try {
                /* This method accepts a java.io.ObjectOutputStream as a parameter */
                ext.writeExternal(this);
            } finally {
                /* DMB:  NOT COMPLETE - Should stop writing in block data format.
                 * Denote the end of this mode by writing a terminator to the stream.
                 */

            }
        }
    }

    public void writeException(Throwable th) throws IOException {
        /* DMB:  NOT COMPLETE - Must write exceptions that occur during serialization
         * to the stream.
         */

    }

    public void writeReset() throws IOException {
        /* DMB:  NOT COMPLETE - Must write the reset byte when the reset() method
         * is called.
         */

    }

/*    public void write(int b) throws IOException {
        out.write(b);
    }
*/
/*    public void write(byte b[], int off, int len) throws IOException {
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }
*/

    public void write(int b) {
        try {
            buf[count++] = (byte) b;
        } catch (ArrayIndexOutOfBoundsException e) {
            byte newbuf[] = new byte[Math.max(buf.length << 1, count)];
            System.arraycopy(buf, 0, newbuf, 0, count - 1);
            buf = newbuf;
        }
    }

    public synchronized void write(byte b[], int off, int len) {
        if (len == 0) return;

        int newcount = count + len;
        if (newcount > buf.length) {
            byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
            System.arraycopy(buf, 0, newbuf, 0, count);
            buf = newbuf;
        }
        System.arraycopy(b, off, buf, count, len);
        count = newcount;
    }

    public void flush() throws IOException {
//      out.flush();
    }

    public byte[] toByteArray() {
        byte newbuf[] = new byte[count];
        System.arraycopy(buf, 0, newbuf, 0, count);
        return newbuf;
    }

    public int size() {
        return count;
    }

    public final void writeBoolean(boolean v) throws IOException {
        write(v ? 1 : 0);
    }

    public final void writeByte(int v) throws IOException {
        write(v);
    }

    public final void writeShort(int v) throws IOException {
        write((v >>> 8) & 0xFF);
        write((v >>> 0) & 0xFF);
    }

    public final void writeChar(int v) throws IOException {
        write((v >>> 8) & 0xFF);
        write((v >>> 0) & 0xFF);
    }

    public final void writeInt(int v) throws IOException {
        write((v >>> 24) & 0xFF);
        write((v >>> 16) & 0xFF);
        write((v >>> 8) & 0xFF);
        write((v >>> 0) & 0xFF);
    }

    public final void writeLong(long v) throws IOException {
        write((int) (v >>> 56) & 0xFF);
        write((int) (v >>> 48) & 0xFF);
        write((int) (v >>> 40) & 0xFF);
        write((int) (v >>> 32) & 0xFF);
        write((int) (v >>> 24) & 0xFF);
        write((int) (v >>> 16) & 0xFF);
        write((int) (v >>> 8) & 0xFF);
        write((int) (v >>> 0) & 0xFF);
    }

    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    public final void writeBytes(String s) throws IOException {
        int tmpLen = s.length();
        for (int i = 0; i < tmpLen; i++) {
            write((byte) s.charAt(i));
        }
    }

    public final void writeChars(String s) throws IOException {
        int tmpLen = s.length();
        for (int i = 0; i < tmpLen; i++) {
            int v = s.charAt(i);
            write((v >>> 8) & 0xFF);
            write((v >>> 0) & 0xFF);
        }
    }

    /*  These are to speed up the writing of strings.
     *  This method is called frequently and placing these here
     *  prevents constant allocation and garbage collection.
     */

    private char[] utfCharBuf = new char[32];

    public final void writeUTF(String str) throws IOException {

        int len = str.length();

        if (utfCharBuf.length < len) utfCharBuf = new char[len];

        str.getChars(0, len, utfCharBuf, 0);

        int mark = count;
        write(0);
        write(0);
        for (int i = 0; i < len; i++) {
            int c = utfCharBuf[i];
            if ((c >= 0x0001) && (c <= 0x007F)) {
                write(c);
            } else if (c > 0x07FF) {
                write(0xE0 | ((c >> 12) & 0x0F));
                write(0x80 | ((c >> 6) & 0x3F));
                write(0x80 | ((c >> 0) & 0x3F));
            } else {
                write(0xC0 | ((c >> 6) & 0x1F));
                write(0x80 | ((c >> 0) & 0x3F));
            }
        }

//         if (tmpUtflen > 65535)throw new UTFDataFormatException();

        len = count - mark - 2;
        buf[mark] = (byte) ((len >>> 8) & 0xFF);
        buf[mark + 1] = (byte) ((len >>> 0) & 0xFF);

    }

    /* Object references are mapped to the wire handles through a hashtable
     * WireHandles are integers generated by the ObjectOutputStream,
     * they need only be unique within a stream.
     * Objects are assigned sequential handles and stored in wireHandle2Object.
     * The handle for an object is its index in wireHandle2Object.
     * Object with the "same" hashcode are chained using wireHash2Handle.
     * The hashcode of objects is used to index through the wireHash2Handle.
     * -1 is the marker for unused cells in wireNextHandle
     */
    private ArrayList wireHandle2Object;
    private int nextWireOffset;

    /* the next five members implement an inline hashtable. */
    private int[] wireHash2Handle;
    private int[] wireNextHandle;
    private int wireHashSizePower = 2;
    private int wireHashLoadFactor = 7;
    private int wireHashCapacity = (1 << wireHashSizePower) * wireHashLoadFactor;

    /*
     * Insert the specified object into the hash array and link if
     * necessary. Put the new object into the hash table and link the
     * previous to it. Newer objects occur earlier in the list.
     */
    private void hashInsert(Object obj, int offset) {
        int hash = System.identityHashCode(obj);
        int index = (hash & 0x7FFFFFFF) % wireHash2Handle.length;
        wireNextHandle[offset] = wireHash2Handle[index];
        wireHash2Handle[index] = offset;
    }

    /*
    * Locate and return if found the handle for the specified object.
    * -1 is returned if the object does not occur in the array of
    * known objects.
    */
    private int findWireOffset(Object obj) {
        int hash = System.identityHashCode(obj);
        int index = (hash & 0x7FFFFFFF) % wireHash2Handle.length;

        for (int handle = wireHash2Handle[index];
             handle >= 0;
             handle = wireNextHandle[handle]) {

            if (wireHandle2Object.get(handle) == obj)
                return handle;
        }
        return -1;
    }

    /* Allocate a handle for an object.
     * The Vector is indexed by the wireHandleOffset
     * and contains the object.
     * Allow caller to specify the hash method for the object.
     */
    private void assignWireOffset(Object obj)
            throws IOException {
        if (nextWireOffset == wireNextHandle.length) {
            int[] oldnexthandles = wireNextHandle;
            wireNextHandle = new int[nextWireOffset * 2];
            System.arraycopy(oldnexthandles, 0,
                    wireNextHandle, 0,
                    nextWireOffset);
        }
        if (nextWireOffset >= wireHashCapacity) {
            growWireHash2Handle();
        }
        wireHandle2Object.add(obj);
        hashInsert(obj, nextWireOffset);
        nextWireOffset++;
        return;
    }

    private void growWireHash2Handle() {

        wireHashSizePower++;
        wireHash2Handle = new int[(1 << wireHashSizePower) - 1];
        Arrays.fill(wireHash2Handle, -1);

        for (int i = 0; i < nextWireOffset; i++) {
            wireNextHandle[i] = 0;
        }

        for (int i = 0; i < wireHandle2Object.size(); i++) {
            hashInsert(wireHandle2Object.get(i), i);
        }

        wireHashCapacity = (1 << wireHashSizePower) * wireHashLoadFactor;
    }

    /*
     * Internal reset function to reinitialize the state of the stream.
     * Reset state of things changed by using the stream.
     */
    private void resetStream() throws IOException {
        if (wireHandle2Object == null) {
            wireHandle2Object = new ArrayList();
            wireNextHandle = new int[4];
            wireHash2Handle = new int[ (1 << wireHashSizePower) - 1];
        } else {

            wireHandle2Object.clear();
            for (int i = 0; i < nextWireOffset; i++) {
                wireNextHandle[i] = 0;
            }
        }
        nextWireOffset = 0;
        Arrays.fill(wireHash2Handle, -1);

        if (classDescStack == null)
            classDescStack = new ArrayStack();
        else
            classDescStack.setSize(0);

    }
}
TOP

Related Classes of org.apache.openejb.util.io.ObjectOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.