Package net.sf.fmj.utility

Source Code of net.sf.fmj.utility.FormatArgUtils$Tokens

package net.sf.fmj.utility;

import java.awt.Dimension;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.format.H261Format;
import javax.media.format.H263Format;
import javax.media.format.IndexedColorFormat;
import javax.media.format.JPEGFormat;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.format.YUVFormat;

import net.sf.fmj.media.BonusAudioFormatEncodings;
import net.sf.fmj.media.BonusVideoFormatEncodings;
import net.sf.fmj.media.format.GIFFormat;
import net.sf.fmj.media.format.PNGFormat;

/**
* A class for converting Format objects to and from strings that can be used as arguments
* in command-line programs, or as parameters in URLs.
*
* The syntax is this: all elements are separated by a colon.
* Everything is uppercase by default, but case is ignored.  Only thing that is lowercase is x in dimension.
* Generally, each item corresponds to a constructor argument.  The Format subclass is inferred from the encoding.
* ? is used to indicate Format.NOT_SPECIFIED (-1).
* floating point values in audio formats are done as integers.
* In audio formats, Big endian is B, little endian is L, signed is S, unsigned is U
* Data types: B is byte[], S is short[], I is int[]
* Dimension: [width]x[height], like "640x480"
* Trailing not specified values may be omitted.
*
* new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 2)
* would be
* LINEAR:44100:16:2
*
*
*
* TODO: support WavAudioFormat, video formats, and other missing audio formats.
*
* @author Ken Larson
*
*/
public class FormatArgUtils
{

  private static final char SEP = ':';
 
  public static final String BYTE_ARRAY = "B";
  public static final String SHORT_ARRAY = "S";
  public static final String INT_ARRAY = "I";
  public static final String NOT_SPECIFIED = "?";
 
  // audio format constants:
  public static final String BIG_ENDIAN = "B";
  public static final String LITTLE_ENDIAN = "L";
  public static final String SIGNED = "S";
  public static final String UNSIGNED = "U";
 
  private static final Map<String, String> formatEncodings = new HashMap<String, String>(); // corect case
  private static final Map<String, Class> formatClasses = new HashMap<String, Class>();
 
  static
  {
     buildFormatMap();
  }
 
  private static final void buildFormatMap()
  {
    addAudioFormat(AudioFormat.LINEAR);
    addAudioFormat(AudioFormat.ULAW); // = "ULAW";
    addAudioFormat(AudioFormat.ULAW_RTP); // = "ULAW/rtp";
    addAudioFormat(AudioFormat.ALAW); // = "alaw";  // strange that this is lower case and ULAW is not...
    addAudioFormat(AudioFormat.IMA4); // = "ima4";
    addAudioFormat(AudioFormat.IMA4_MS); // = "ima4/ms";
    addAudioFormat(AudioFormat.MSADPCM); // = "msadpcm";
    addAudioFormat(AudioFormat.DVI); // = "dvi";
    addAudioFormat(AudioFormat.DVI_RTP); // = "dvi/rtp";
    addAudioFormat(AudioFormat.G723); // = "g723";
    addAudioFormat(AudioFormat.G723_RTP); // = "g723/rtp";
    addAudioFormat(AudioFormat.G728); // = "g728";
    addAudioFormat(AudioFormat.G728_RTP); // = "g728/rtp";
    addAudioFormat(AudioFormat.G729); // = "g729";
    addAudioFormat(AudioFormat.G729_RTP); // = "g729/rtp";
    addAudioFormat(AudioFormat.G729A); // = "g729a";
    addAudioFormat(AudioFormat.G729A_RTP); // = "g729a/rtp";
    addAudioFormat(AudioFormat.GSM); // = "gsm";
    addAudioFormat(AudioFormat.GSM_MS); // = "gsm/ms";
    addAudioFormat(AudioFormat.GSM_RTP); // = "gsm/rtp";
    addAudioFormat(AudioFormat.MAC3); // = "MAC3";
    addAudioFormat(AudioFormat.MAC6); // = "MAC6";
    addAudioFormat(AudioFormat.TRUESPEECH); // = "truespeech";
    addAudioFormat(AudioFormat.MSNAUDIO); // = "msnaudio";
    addAudioFormat(AudioFormat.MPEGLAYER3); // = "mpeglayer3";
    addAudioFormat(AudioFormat.VOXWAREAC8); // = "voxwareac8";
    addAudioFormat(AudioFormat.VOXWAREAC10); // = "voxwareac10";
    addAudioFormat(AudioFormat.VOXWAREAC16); // = "voxwareac16";
    addAudioFormat(AudioFormat.VOXWAREAC20); // = "voxwareac20";
    addAudioFormat(AudioFormat.VOXWAREMETAVOICE); // = "voxwaremetavoice";
    addAudioFormat(AudioFormat.VOXWAREMETASOUND); // = "voxwaremetasound";
    addAudioFormat(AudioFormat.VOXWARERT29H); // = "voxwarert29h";
    addAudioFormat(AudioFormat.VOXWAREVR12); // = "voxwarevr12";
    addAudioFormat(AudioFormat.VOXWAREVR18); // = "voxwarevr18";
    addAudioFormat(AudioFormat.VOXWARETQ40); // = "voxwaretq40";
    addAudioFormat(AudioFormat.VOXWARETQ60); // = "voxwaretq60";
    addAudioFormat(AudioFormat.MSRT24); // = "msrt24";
    addAudioFormat(AudioFormat.MPEG); // = "mpegaudio";
    addAudioFormat(AudioFormat.MPEG_RTP); // = "mpegaudio/rtp";
    addAudioFormat(AudioFormat.DOLBYAC3); // = "dolbyac3";
   
    for (String e : BonusAudioFormatEncodings.ALL)
      addAudioFormat(e);
   
   
    // TODO: MpegEncoding using
//    MpegEncoding.MPEG1L1,
//    MpegEncoding.MPEG1L2,
//    MpegEncoding.MPEG1L3,
//    MpegEncoding.MPEG2DOT5L1,
//    MpegEncoding.MPEG2DOT5L2,
//    MpegEncoding.MPEG2DOT5L3,
//    MpegEncoding.MPEG2L1,
//    MpegEncoding.MPEG2L2,
//    MpegEncoding.MPEG2L3,
    // TODO: VorbisEncoding using VorbisEncoding.VORBISENC
   
   
    // Video formats:

    addVideoFormat(VideoFormat.CINEPAK); // ="cvid";
    addFormat(VideoFormat.JPEG, JPEGFormat.class);
    addVideoFormat(VideoFormat.JPEG_RTP); // ="jpeg/rtp";
    addVideoFormat(VideoFormat.MPEG); // ="mpeg";
    addVideoFormat(VideoFormat.MPEG_RTP); // ="mpeg/rtp";
    addFormat(VideoFormat.H261, H261Format.class);
    addVideoFormat(VideoFormat.H261_RTP); // ="h261/rtp";
    addFormat(VideoFormat.H263, H263Format.class);
    addVideoFormat(VideoFormat.H263_RTP); // ="h263/rtp";
    addVideoFormat(VideoFormat.H263_1998_RTP); // ="h263-1998/rtp";
    addFormat(VideoFormat.RGB, RGBFormat.class);
    addFormat(VideoFormat.YUV, YUVFormat.class);
    addFormat(VideoFormat.IRGB, IndexedColorFormat.class);
    addVideoFormat(VideoFormat.SMC); // ="smc";
    addVideoFormat(VideoFormat.RLE); // ="rle";
    addVideoFormat(VideoFormat.RPZA); // ="rpza";
    addVideoFormat(VideoFormat.MJPG); // ="mjpg";
    addVideoFormat(VideoFormat.MJPEGA); // ="mjpa";
    addVideoFormat(VideoFormat.MJPEGB); // ="mjpb";
    addVideoFormat(VideoFormat.INDEO32); // ="iv32";
    addVideoFormat(VideoFormat.INDEO41); // ="iv41";
    addVideoFormat(VideoFormat.INDEO50); // ="iv50";
   
    // TODO: AviVideoFormat
   
    addFormat(BonusVideoFormatEncodings.GIF, GIFFormat.class);
    addFormat(BonusVideoFormatEncodings.PNG, PNGFormat.class);

  }
 
 
  public static String toString(Format f)
  {
   
    final List<String> list = new ArrayList<String>();
   
    list.add(f.getEncoding().toUpperCase());
   
    if (f instanceof AudioFormat)
    {
      final AudioFormat af = (AudioFormat) f;
      list.add(intToStr((int) af.getSampleRate()));
      list.add(intToStr(af.getSampleSizeInBits()));
     
      list.add(intToStr(af.getChannels()));
      list.add(endianToStr(af.getEndian()));
      list.add(signedToStr(af.getSigned()));
      list.add(intToStr(af.getFrameSizeInBits()));
      list.add(intToStr((int) af.getFrameRate()));
      if (af.getDataType() != null && af.getDataType() != Format.byteArray)
        list.add(dataTypeToStr(af.getDataType()));
     

     
    }
    else if (f instanceof VideoFormat)
    {
      final VideoFormat vf = (VideoFormat) f;
      if (f.getClass() == JPEGFormat.class)
      {
        final JPEGFormat jf = (JPEGFormat) vf;
        list.add(dimensionToStr(jf.getSize()));
        list.add(intToStr(jf.getMaxDataLength()));
        if (jf.getDataType() != null && jf.getDataType() != Format.byteArray)
          list.add(dataTypeToStr(jf.getDataType()));
        list.add(floatToStr(jf.getFrameRate()));
        // TODO: Q, decimation
       
      }
      else if (f.getClass() == GIFFormat.class)
      {
        final GIFFormat gf = (GIFFormat) vf;
        list.add(dimensionToStr(gf.getSize()));
        list.add(intToStr(gf.getMaxDataLength()));
        if (gf.getDataType() != null && gf.getDataType() != Format.byteArray)
          list.add(dataTypeToStr(gf.getDataType()));
        list.add(floatToStr(gf.getFrameRate()));
       
      }
      else if (f.getClass() == PNGFormat.class)
      {
        final PNGFormat pf = (PNGFormat) vf;
        list.add(dimensionToStr(pf.getSize()));
        list.add(intToStr(pf.getMaxDataLength()));
        if (pf.getDataType() != null && pf.getDataType() != Format.byteArray)
          list.add(dataTypeToStr(pf.getDataType()));
        list.add(floatToStr(pf.getFrameRate()));
       
      }
      else if (f.getClass() == VideoFormat.class)
      {
        list.add(dimensionToStr(vf.getSize()));
        list.add(intToStr(vf.getMaxDataLength()));
        if (vf.getDataType() != null && vf.getDataType() != Format.byteArray)
          list.add(dataTypeToStr(vf.getDataType()));
        list.add(floatToStr(vf.getFrameRate()));
       
      }
      else if (f.getClass() == RGBFormat.class)
      {
        final RGBFormat rf = (RGBFormat) vf;
        list.add(dimensionToStr(vf.getSize()));
        list.add(intToStr(vf.getMaxDataLength()));
        if (vf.getDataType() != null && vf.getDataType() != Format.byteArray)
          list.add(dataTypeToStr(vf.getDataType()));
        list.add(floatToStr(vf.getFrameRate()));
        list.add(intToStr(rf.getBitsPerPixel()));
        list.add(intToStr(rf.getRedMask()))// TODO: hex?
        list.add(intToStr(rf.getGreenMask()));
        list.add(intToStr(rf.getBlueMask()));
        list.add(intToStr(rf.getPixelStride()));
        list.add(intToStr(rf.getLineStride()));
        list.add(intToStr(rf.getFlipped()));    // TODO: use a string code for this?
        list.add(rgbFormatEndianToStr(rf.getEndian()));

      }
      else
        throw new IllegalArgumentException("Unknown or unsupported format: " + f);
    }
    else
    throw new IllegalArgumentException("" + f);
    }
   
    // remove any default values from the end.
    while (list.get(list.size() - 1) == null || list.get(list.size() - 1).equals(NOT_SPECIFIED))
      list.remove(list.size() - 1);
   
    final StringBuilder b = new StringBuilder();

    for (int i = 0; i < list.size(); ++i)
    {  if (i > 0)
        b.append(SEP);
      b.append(list.get(i));
    }
   
   
    return b.toString();
  }
 
  private static final String intToStr(int i)
  {
    if (i == Format.NOT_SPECIFIED)
      return NOT_SPECIFIED;
    else
      return "" + i;
  }
 
  private static final String floatToStr(float v)
  {
    if (v == Format.NOT_SPECIFIED)
      return NOT_SPECIFIED;
    else
      return "" + v;
  }
 
  private static final String dimensionToStr(Dimension d)
  {
    if (d == null)
      return NOT_SPECIFIED;
    return ((int) d.getWidth()) + "x" + ((int) d.getHeight());
  }
 
 
  private static final String dataTypeToStr(Class clazz)
  {
    if (clazz == null)
    {  return NOT_SPECIFIED;
    }
    if (clazz == Format.byteArray)
    {  return BYTE_ARRAY;
    }
    if (clazz == Format.shortArray)
    {  return SHORT_ARRAY;
    }
    if (clazz == Format.intArray)
    {  return INT_ARRAY;
    }
   
    throw new IllegalArgumentException("" + clazz);

  }
 
  private static final String endianToStr(int endian)
  {
    if (endian == Format.NOT_SPECIFIED)
      return NOT_SPECIFIED;
    else if (endian == AudioFormat.BIG_ENDIAN)
      return BIG_ENDIAN;
    else if (endian == AudioFormat.LITTLE_ENDIAN)
      return LITTLE_ENDIAN;
    else
      throw new IllegalArgumentException("Unknown endianness: " + endian);
   
  }
 
  private static final String rgbFormatEndianToStr(int endian)
  {
    if (endian == Format.NOT_SPECIFIED)
      return NOT_SPECIFIED;
    else if (endian == RGBFormat.BIG_ENDIAN)
      return BIG_ENDIAN;
    else if (endian == RGBFormat.LITTLE_ENDIAN)
      return LITTLE_ENDIAN;
    else
      throw new IllegalArgumentException("Unknown endianness: " + endian);
   
  }
 
  private static final String signedToStr(int signed)
  {
    if (signed == Format.NOT_SPECIFIED)
      return NOT_SPECIFIED;
    else if (signed == AudioFormat.SIGNED)
      return SIGNED;
    else if (signed == AudioFormat.UNSIGNED)
      return UNSIGNED;
    else
      throw new IllegalArgumentException("Unknown signedness: " + signed);
   
  }
 
 
 
 
  private static final void addAudioFormat(String s)
  {
    addFormat(s, AudioFormat.class);
  }
 
  private static final void addVideoFormat(String s)
  {
    addFormat(s, VideoFormat.class);
  }
 
  private static final void addFormat(String s, Class clazz)
  {
   
    formatClasses.put(s.toLowerCase(), clazz);
    formatEncodings.put(s.toLowerCase(), s);
  }
 

  private static class Tokens
  {
    private final String[] items;
    private int ix;
   
    public Tokens(String[] items)
    {
      super();
      this.items = items;
      ix = 0;
    }
   
    public String nextString()
    {
      return nextString(null);
    }
   
    public String nextString(String defaultResult)
    {
      if (ix >= items.length)
        return defaultResult;
     
      final String result = items[ix];
      ++ix;
      return result;
    }
   
    public double nextDouble() throws ParseException
    {
      final String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      try
      {
       
        return Double.parseDouble(s);
      }
      catch (NumberFormatException e)
      { 
        throw new ParseException("Expected double: " + s, -1);
      }
    }
   
    public float nextFloat() throws ParseException
    {
      final String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      try
      {
       
        return Float.parseFloat(s);
      }
      catch (NumberFormatException e)
      { 
        throw new ParseException("Expected float: " + s, -1);
      }
    }
   
    public int nextInt() throws ParseException
    {
      final String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      try
      {
       
        return Integer.parseInt(s);
      }
      catch (NumberFormatException e)
      { 
        throw new ParseException("Expected integer: " + s, -1);
      }
    }
   
    public int nextSigned()  throws ParseException
    {
      String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      s = s.toUpperCase();
     
      if (s.equals(UNSIGNED))
        return AudioFormat.UNSIGNED;
      else if (s.equals(SIGNED))
        return AudioFormat.SIGNED;
      else
        throw new ParseException("Expected one of [" + UNSIGNED + "," + UNSIGNED + "]: " + s, -1);
    }
   
    public int nextEndian()  throws ParseException
    {
      String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      s = s.toUpperCase();
     
      if (s.equals(BIG_ENDIAN))
        return AudioFormat.BIG_ENDIAN;
      else if (s.equals(LITTLE_ENDIAN))
        return AudioFormat.LITTLE_ENDIAN;
      else
        throw new ParseException("Expected one of [" + BIG_ENDIAN + "," + LITTLE_ENDIAN + "]: " + s, -1);
    }
    public int nextRGBFormatEndian()  throws ParseException
    {
      String s = nextString();
      if (s == null)
        return Format.NOT_SPECIFIED;
     
      if (s.equals(NOT_SPECIFIED))
        return Format.NOT_SPECIFIED;
     
      s = s.toUpperCase();
     
      if (s.equals(BIG_ENDIAN))
        return RGBFormat.BIG_ENDIAN;
      else if (s.equals(LITTLE_ENDIAN))
        return RGBFormat.LITTLE_ENDIAN;
      else
        throw new ParseException("Expected one of [" + BIG_ENDIAN + "," + LITTLE_ENDIAN + "]: " + s, -1);
    }
   
    public Class nextDataType() throws ParseException
    {
      String s = nextString();
      if (s == null)
        return null;
     
      if (s.equals(NOT_SPECIFIED))
        return null;
     
      s = s.toUpperCase();
     
      if (s.equals(BYTE_ARRAY))
        return Format.byteArray;
      else if (s.equals(SHORT_ARRAY))
        return Format.shortArray;
      else if (s.equals(INT_ARRAY))
        return Format.intArray;
      else
        throw new ParseException("Expected one of [" + BYTE_ARRAY + "," + SHORT_ARRAY + "," + INT_ARRAY + "]: " + s, -1);
     
    }
   
    public Dimension nextDimension() throws ParseException
    {
      String s = nextString();
      if (s == null)
        return null;
     
      if (s.equals(NOT_SPECIFIED))
        return null;
     
      s = s.toUpperCase();
     
      String[] strings = s.split("X");
      if (strings.length != 2)
        throw new ParseException("Expected WIDTHxHEIGHT: " + s, -1);
      int width;
      int height;
     
      try
      {
        width = Integer.parseInt(strings[0]);
      }
      catch (NumberFormatException e)
      {  throw new ParseException("Expected integer: " + strings[0], -1);
      }
      try
      {
        height = Integer.parseInt(strings[1]);
      }
      catch (NumberFormatException e)
      {  throw new ParseException("Expected integer: " + strings[1], -1);
      }
     
      return new Dimension(width, height);
    }
   
   
  }
 

 
 
 
  public static Format parse(String s) throws ParseException
  {
    final String[] strings = s.split("" + SEP);
    final Tokens t = new Tokens(strings);
   
    int ix = 0;
    final String encodingIgnoreCase = t.nextString(null);
   
    if (encodingIgnoreCase == null)
      throw new ParseException("No encoding specified", 0);
   
    final Class formatClass = formatClasses.get(encodingIgnoreCase.toLowerCase());
    if (formatClass == null)
      throw new ParseException("Unknown encoding: " + encodingIgnoreCase, -1);
   
    final String encoding = formatEncodings.get(encodingIgnoreCase.toLowerCase());
    if (encoding == null)
      throw new ParseException("Unknown encoding: " + encodingIgnoreCase, -1);
   
    if (AudioFormat.class.isAssignableFrom(formatClass))
    {
      final double sampleRate = t.nextDouble();
      final int sampleSizeInBits = t.nextInt();
      final int channels = t.nextInt();
      final int endian = t.nextEndian();
      final int signed = t.nextSigned();
      final int frameSizeInBits = t.nextInt();
      final double frameRate = t.nextDouble();
      Class dataType = t.nextDataType();
      if (dataType == null)
        dataType = Format.byteArray;  // default
     
      return new AudioFormat(encoding, sampleRate,
          sampleSizeInBits, channels, endian, signed,
          frameSizeInBits, frameRate, dataType);
     
    }
    else if (VideoFormat.class.isAssignableFrom(formatClass))
    {
      if (formatClass == JPEGFormat.class)
      {
        final java.awt.Dimension size = t.nextDimension();
              final int maxDataLength = t.nextInt();
              Class dataType = t.nextDataType();
        if (dataType == null)
          dataType = Format.byteArray;  // default
              final float frameRate = t.nextFloat();
              final int q = Format.NOT_SPECIFIED; // TODO
              final int dec = Format.NOT_SPECIFIED; // TODO
             
        return new JPEGFormat(size, maxDataLength, dataType, frameRate, q, dec);
      }
      else if (formatClass == GIFFormat.class)
      {
        final java.awt.Dimension size = t.nextDimension();
              final int maxDataLength = t.nextInt();
              Class dataType = t.nextDataType();
        if (dataType == null)
          dataType = Format.byteArray;  // default
              final float frameRate = t.nextFloat();
             
        return new GIFFormat(size, maxDataLength, dataType, frameRate);
      }
      else if (formatClass == PNGFormat.class)
      {
        final java.awt.Dimension size = t.nextDimension();
              final int maxDataLength = t.nextInt();
              Class dataType = t.nextDataType();
        if (dataType == null)
          dataType = Format.byteArray;  // default
              final float frameRate = t.nextFloat();
             
        return new PNGFormat(size, maxDataLength, dataType, frameRate);
      }
      else if (formatClass == VideoFormat.class)
      {
        final java.awt.Dimension size = t.nextDimension();
              final int maxDataLength = t.nextInt();
              Class dataType = t.nextDataType();
        if (dataType == null)
          dataType = Format.byteArray;  // default
              final float frameRate = t.nextFloat();
             
        return new VideoFormat(encoding, size, maxDataLength, dataType, frameRate);
      }
      else if (formatClass == RGBFormat.class)
      {
        final java.awt.Dimension size = t.nextDimension();
              final int maxDataLength = t.nextInt();
              Class dataType = t.nextDataType();
        if (dataType == null)
          dataType = Format.byteArray;  // default
              final float frameRate = t.nextFloat();
              final int bitsPerPixel = t.nextInt();
        final int red = t.nextInt();
        final int green = t.nextInt();
        final int blue = t.nextInt();
        final int pixelStride = t.nextInt();
        final int lineStride = t.nextInt();
        final int flipped = t.nextInt();
        final int endian = t.nextRGBFormatEndian();
             
        if (pixelStride == -1 && lineStride == -1 && flipped == -1 && endian == -1)
          return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue);

        return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue, pixelStride, lineStride, flipped, endian);
      }
     
//      public RGBFormat(java.awt.Dimension size, int maxDataLength,
//          Class dataType, float frameRate, int bitsPerPixel,
//          int red, int green, int blue, int pixelStride, int lineStride,
//          int flipped, int endian)
      // TODO: others
      throw new RuntimeException("TODO: Unknown class: " + formatClass);
    }
    else
    {
      throw new RuntimeException("Unknown class: " + formatClass);
    }
     
   
   
  }
}
TOP

Related Classes of net.sf.fmj.utility.FormatArgUtils$Tokens

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.