/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package hamsam.protocol.yahoo;
import hamsam.api.Message;
import hamsam.api.MessageComponent;
import hamsam.api.SmileyComponent;
import hamsam.api.TextComponent;
import hamsam.api.URLComponent;
import java.awt.Color;
import java.awt.Font;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Icon;
import javax.swing.ImageIcon;
/**
* A collection of utility methods.
*
* This class can neither be instantiated nor be inherited from.
*
* @author Raghu
*/
public final class Util
{
/**
* This array contains the descriptive names for all Yahoo
* smileys.
*/
private static String[] smileyNames = {
"happy", "sad", "wink", "big grin", "batting eyelashes", "confused", "love struck",
"blushing", "tongue", "kiss", "shock", "angry", "smug", "cool", "worried", "devilish",
"crying", "laughing", "straight face", "raised eyebrow", "angel", "nerd",
"talk to the hand", "sleep", "rolling eyes", "sick", "shhh", "not talking", "clown",
"silly", "tired", "drooling", "thinking", "d'oh!", "applause", "pig", "cow", "monkey",
"chicken", "rose", "good luck", "flag", "pumpkin", "coffee", "idea", "skull", "alien 1",
"alien 2", "frustrated", "cowboy", "praying", "hypnotized", "money eyes", "whistling",
"liar liar", "beat up", "peace", "shame on you", "dancing", "hugs"
};
/**
* This array contains the alternate text for all Yahoo
* smileys.
*/
private static String[][] smileyTexts = {
{ ":)" }, { ":(" }, { ";)" }, { ":D" }, { ";;)" }, { ":-/" },
{ ":x" }, { ":\">" }, { ":p" }, { ":*" }, { ":O" }, { "X-(" },
{ ":>" }, { "B-)" }, { ":-s" }, { ">:)" }, { ":((" }, { ":))" },
{ ":|" }, { "/:)" }, { "0:)" }, { ":-B" }, { "=;" }, { "I-)" },
{ "8-|" }, { ":-&" }, { ":-$" }, { "[-(" }, { ":o)" }, { "8-}" },
{ "(:|" }, { "=P~" }, { ":-?" }, { "#-o" }, { "=D>" }, { ":@)" },
{ "3:-O" }, { ":(|)" }, { "~:>" }, { "@};-" }, { "%%-" }, { "**==" },
{ "(~~)" }, { "~o)" }, { "*-:)" }, { "8-X" }, { "=:)" }, { ">-)" },
{ ":-L" }, { "<):)" }, { "[-o<" }, { "@-)" }, { "$-)" }, { ":-\"" },
{ ":^o" }, { "b-(" }, { ":)>-" }, { "[-X" }, { "\\:D/" }, { ">:D<" }
};
/**
* Array of all available Yahoo smileys.
*/
private static SmileyComponent[] smileys = new SmileyComponent[60];
/**
* This private constructor prevents instantiation of this
* class.
*/
private Util()
{
}
/**
* Construct a message object from a message string received from Yahoo.
*
* @param message the message string received from Yahoo.
* @return the equivalent message object.
*/
public static Message parseYahooMessage(String message)
{
// We search for message components in this order.
// 1. URLComponent
// 2. SmileyComponent
// 3. TextComponent
MessageComponent[] comp = parseForURLs(new TextComponent(message));
comp = parseForSmileys(comp);
comp = parseForText(comp);
Message ret = new Message();
for(int i = 0; i < comp.length; i++)
ret.addComponent(comp[i]);
return ret;
}
/**
* Convert a yahoo string equivalent to a given message object.
*
* @param message the message object to be converted.
* @return the equivalent yahoo string.
*/
public static String messageToYahooString(Message message)
{
StringBuffer ret = new StringBuffer();
Enumeration e = message.getComponents();
while(e.hasMoreElements())
{
MessageComponent comp = (MessageComponent) e.nextElement();
if(comp instanceof TextComponent)
{
TextComponent txt = (TextComponent) comp;
Color color = txt.getColor();
if(color != null)
{
ret.append("\033[#");
ret.append(Integer.toHexString(color.getRed()));
ret.append(Integer.toHexString(color.getGreen()));
ret.append(Integer.toHexString(color.getBlue()));
ret.append('m');
}
Font font = txt.getFont();
if(font != null)
{
ret.append("<font face=\"" + font.getFontName() + "\" ");
ret.append("size=\"" + font.getSize() + "\">");
}
ret.append(txt.getSequence());
}
else if(comp instanceof SmileyComponent)
{
SmileyComponent smiley = (SmileyComponent) comp;
ret.append(smiley.getText());
}
else if(comp instanceof URLComponent)
{
URLComponent url = (URLComponent) comp;
ret.append(url.getLinkText());
}
}
return ret.toString();
}
/**
* Returns an array of all yahoo smileys.
*
* @return array of all yahoo smiley components.
*/
public static SmileyComponent[] loadSmileys()
{
ClassLoader cl = ClassLoader.getSystemClassLoader();
// load all 60 smileys
for(int i = 1; i <= 60; i++)
{
URL url;
if(i < 10)
url = cl.getResource("hamsam/protocol/yahoo/images/0" + i + ".gif");
else
url = cl.getResource("hamsam/protocol/yahoo/images/" + i + ".gif");
if(url == null)
smileys[i - 1] = new SmileyComponent(null, smileyTexts[i - 1], smileyNames[i - 1]);
else
{
Icon icon = new ImageIcon(url);
smileys[i - 1] = new SmileyComponent(icon, smileyTexts[i - 1], smileyNames[i - 1]);
}
}
return smileys;
}
private static Pattern urlPattern = Pattern.compile("http://\\S+|ftp://\\S+|www\\.\\S+");
/**
* Parses a character sequence from a given text component to URL components
* and text components representing the remaining sequence of characters.
* As of now, Any word begining with "http://", "ftp://", or "www." are
* considered as URLS. This may change in future versions of the library.
*
* @param comp the input text component.
* @return the sequence of resultant message components.
*/
private static MessageComponent[] parseForURLs(TextComponent comp)
{
StringBuffer sb = new StringBuffer();
sb.append(comp.getSequence());
List ret = new ArrayList();
Matcher m = urlPattern.matcher(sb);
int txtStart = 0;
while(m.find())
{
int urlStart = m.start();
int urlEnd = m.end();
if(txtStart <= urlStart - 1)
ret.add(new TextComponent(sb.substring(txtStart, urlStart)));
String linkText = sb.substring(urlStart, urlEnd);
try
{
if(linkText.startsWith("www"))
ret.add(new URLComponent(linkText, new URL("http://" + linkText)));
else
ret.add(new URLComponent(linkText, new URL(linkText)));
}
catch(MalformedURLException e)
{
ret.add(new TextComponent(linkText));
}
txtStart = urlEnd;
}
if(txtStart < sb.length())
ret.add(new TextComponent(sb.substring(txtStart)));
return (MessageComponent[]) ret.toArray(new MessageComponent[0]);
}
/**
* Parse and get smiley components.
*
* <p>
* The input to this method is an array of message components, each of which
* may be either a text component or a url component. This method selects all
* the text components and splits them to text components and smiley components.
*
* @param comp the input array of message components
* @return the output array of message components.
*/
private static MessageComponent[] parseForSmileys(MessageComponent[] comp)
{
Vector ret = new Vector();
for(int i = 0; i < comp.length; i++)
{
if(comp[i] instanceof TextComponent)
{
TextComponent txt = (TextComponent) comp[i];
MessageComponent[] msgs = splitForSmileys(txt);
for(int j = 0; j < msgs.length; j++)
ret.add(msgs[j]);
}
else
ret.add(comp[i]);
}
return (MessageComponent[]) ret.toArray(new MessageComponent[0]);
}
/**
* Split a text component to smileys and plain text.
*
* @param comp the text component to be split.
* @return an array of message components after split.
*/
private static MessageComponent[] splitForSmileys(TextComponent comp)
{
char[] chars = comp.getSequence();
if(chars == null || chars.length == 0)
return new MessageComponent[0];
String text = new String(chars);
List ret = new ArrayList();
// Find the longest smiley in this text
int foundPos = -1;
int foundSmiley = -1;
int foundLength = -1;
for(int i = 0; i < smileyTexts.length; i++)
{
String[] oneSmiley = smileyTexts[i];
for(int j = 0; j < oneSmiley.length; j++)
{
int len = oneSmiley[j].length();
if(len <= foundLength)
continue;
int pos = text.indexOf(oneSmiley[j]);
if(pos == -1)
continue;
foundPos = pos;
foundSmiley = i;
foundLength = len;
}
}
// split this text component to two - one on the
// left side of the smiley and one on the right side.
// recursively do a split for smileys on both these
// pieces.
if(foundPos != -1)
{
String left = text.substring(0, foundPos);
String right = text.substring(foundPos + foundLength);
MessageComponent[] leftComp = splitForSmileys(new TextComponent(left));
MessageComponent[] rightComp = splitForSmileys(new TextComponent(right));
for(int i = 0; i < leftComp.length; i++)
ret.add(leftComp[i]);
ret.add(smileys[foundSmiley]);
for(int i = 0; i < rightComp.length; i++)
ret.add(rightComp[i]);
}
else
ret.add(new TextComponent(text));
return (MessageComponent[]) ret.toArray(new MessageComponent[0]);
}
/**
* Parse and get text components.
*
* <p>
* The input to this method is an array of message components, each of which
* may be any type of message component or a url component. This method selects
* all the text components out of these and pases for font and color information.
*
* @param comp the input array of message components
* @return the output array of message components.
*/
private static MessageComponent[] parseForText(MessageComponent[] comp)
{
Vector ret = new Vector();
for(int i = 0; i < comp.length; i++)
{
if(comp[i] instanceof TextComponent)
{
TextComponent txt = (TextComponent) comp[i];
MessageComponent[] msgs = splitFontAndColor(txt);
for(int j = 0; j < msgs.length; j++)
ret.add(msgs[j]);
}
else
ret.add(comp[i]);
}
return (MessageComponent[]) ret.toArray(new MessageComponent[0]);
}
private static Pattern fontColorPattern = Pattern.compile("(<font\\s+(face\\s*=\\s*\"(.+)\"\\s*)?size\\s*=\\s*\"(\\d+)\">)|(\033\\[((\\d+)|(#([0-9a-fA-F]+)))m)");
/**
* Parses a text component to extract font and color information.
*
* @param comp the input text component with a sequence of characters
* encoding the data as well as font/color information.
* @return an array of text components with proper font and color information.
*/
private static MessageComponent[] splitFontAndColor(TextComponent comp)
{
int currentFontStyle = Font.PLAIN;
int currentFontSize = 10;
Font currentFont = new Font(null, currentFontStyle, currentFontSize);
Color currentColor = Color.black;
List ret = new ArrayList();
StringBuffer sb = new StringBuffer();
sb.append(comp.getSequence());
Matcher m = fontColorPattern.matcher(sb);
int txtStart = 0;
while(m.find())
{
int txtEnd = m.end();
if(txtStart < txtEnd)
{
String text = sb.substring(txtStart, txtEnd);
ret.add(new TextComponent(text, currentFont, currentColor));
}
txtStart = txtEnd;
String fontFace = m.group(3);
String fontSize = m.group(4);
String colorCodeStr = m.group(7);
String colorStr = m.group(9);
if(fontFace != null)
currentFont = new Font(fontFace, currentFontStyle, currentFontSize);
if(fontSize != null)
{
currentFontSize = Integer.parseInt(fontSize);
currentFont = currentFont.deriveFont((float)currentFontSize);
}
if(colorCodeStr != null)
{
switch(Integer.parseInt(colorCodeStr))
{
case 38:
currentColor = new Color(0xff0000);
break;
case 34:
currentColor = new Color(0x00ff00);
break;
case 39:
currentColor = new Color(0xffff00);
break;
case 31:
currentColor = new Color(0x0000ff);
break;
case 36:
currentColor = new Color(0xff00ff);
break;
case 32:
currentColor = new Color(0x00ffff);
break;
case 37:
currentColor = new Color(0xff8000);
break;
case 35:
currentColor = new Color(0xff0080);
break;
case 33:
currentColor = new Color(0x808080);
break;
}
}
if(colorStr != null)
{
currentColor = new Color(Integer.parseInt(colorStr, 16));
}
}
if(txtStart < sb.length())
{
String text = sb.substring(txtStart);
ret.add(new TextComponent(text, currentFont, currentColor));
}
return (MessageComponent[]) ret.toArray(new MessageComponent[0]);
}
/**
* Converts a byte array to equivalent char array. This method
* assumes that the byte values are in the range 0-255.
*
* @param byteArr the byte array for conversion
* @return equivalent character array
*/
static char[] byteArrayToCharArray(byte[] byteArr)
{
char[] result = new char[byteArr.length];
for(int i = 0; i < byteArr.length; i++)
result[i] = (char)(byteArr[i] >= 0 ? byteArr[i] : 256 + byteArr[i]);
return result;
}
}