/**
* Copyright (c) 2008-2010 The Sakai Foundation
*
* Licensed under the Educational Community 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.osedu.org/licenses/ECL-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.sakaiproject.profile2.util;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.swing.ImageIcon;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.log4j.Logger;
import org.sakaiproject.util.FormattedText;
import org.sakaiproject.util.ResourceLoader;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class ProfileUtils {
private static final Logger log = Logger.getLogger(ProfileUtils.class);
/**
* Check content type against allowed types. only JPEG,GIF and PNG are support at the moment
*
* @param contentType string of the content type determined by some image parser
*/
public static boolean checkContentTypeForProfileImage(String contentType) {
ArrayList<String> allowedTypes = new ArrayList<String>();
allowedTypes.add("image/jpeg");
allowedTypes.add("image/gif");
allowedTypes.add("image/png");
//Adding MIME types that Internet Explorer returns PRFL-98
allowedTypes.add("image/x-png");
allowedTypes.add("image/pjpeg");
allowedTypes.add("image/jpg");
if(allowedTypes.contains(contentType)) {
return true;
}
return false;
}
/**
* Scale an image so one side is a maximum of maxSize in pixels.
*
* @param imageData bytes of the original image
* @param maxSize maximum dimension in px that the image should have on any one side
*/
public static byte[] scaleImage(byte[] imageData, int maxSize) {
log.debug("Scaling image...");
// Get the image
Image inImage = new ImageIcon(imageData).getImage();
// Determine the scale (we could change this to only determine scale from one dimension, ie the width only?)
double scale = (double) maxSize / (double) inImage.getHeight(null);
if (inImage.getWidth(null) > inImage.getHeight(null)) {
scale = (double) maxSize / (double) inImage.getWidth(null);
}
/*
log.debug("===========Image scaling============");
log.debug("WIDTH: " + inImage.getWidth(null));
log.debug("HEIGHT: " + inImage.getHeight(null));
log.debug("SCALE: " + scale);
log.debug("========End of image scaling========");
*/
//if image is smaller than desired image size (ie scale is larger) just return the original image bytes
if (scale >= 1.0d) {
return imageData;
}
// Determine size of new image.
// One of the dimensions should equal maxSize.
int scaledW = (int) (scale * inImage.getWidth(null));
int scaledH = (int) (scale * inImage.getHeight(null));
// Create an image buffer in which to paint on.
BufferedImage outImage = new BufferedImage(scaledW, scaledH, BufferedImage.TYPE_INT_RGB);
// Set the scale.
AffineTransform tx = new AffineTransform();
//scale
tx.scale(scale, scale);
// Paint image.
Graphics2D g2d = outImage.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.drawImage(inImage, tx, null);
g2d.dispose();
// JPEG-encode the image
// and write to file.
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(os);
encoder.encode(outImage);
os.close();
log.debug("Scaling done.");
} catch (IOException e) {
log.error("Scaling image failed.");
}
return os.toByteArray();
}
/**
* Convert a Date into a String according to format
*
* @param date date to convert
* @param format format in SimpleDateFormat syntax
*/
public static String convertDateToString(Date date, String format) {
if(date == null || "".equals(format)) {
throw new IllegalArgumentException("Null Argument in Profile.convertDateToString()");
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
String dateStr = dateFormat.format(date);
log.debug("Profile.convertDateToString(): Input date: " + date.toString());
log.debug("Profile.convertDateToString(): Converted date string: " + dateStr);
return dateStr;
}
/**
* Convert a string into a Date object (reverse of above
*
* @param dateStr date string to convert
* @param format format of the input date in SimpleDateFormat syntax
*/
public static Date convertStringToDate(String dateStr, String format) {
if("".equals(dateStr) || "".equals(format)) {
throw new IllegalArgumentException("Null Argument in Profile.convertStringToDate()");
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.US);
try {
Date date = dateFormat.parse(dateStr);
log.debug("Profile.convertStringToDate(): Input date string: " + dateStr);
log.debug("Profile.convertStringToDate(): Converted date: " + date.toString());
return date;
} catch (Exception e) {
log.error("Profile.convertStringToDate() failed. " + e.getClass() + ": " + e.getMessage());
return null;
}
}
/**
* Get the localised name of the day (ie Monday for en, Maandag for nl)
* @param day int according to Calendar.DAY_OF_WEEK
* @param locale locale to render dayname in
* @return
*/
public static String getDayName(int day, Locale locale) {
//localised daynames
String dayNames[] = new DateFormatSymbols(locale).getWeekdays();
String dayName = null;
try {
dayName = dayNames[day];
} catch (Exception e) {
log.error("Profile.getDayName() failed. " + e.getClass() + ": " + e.getMessage());
}
return dayName;
}
/**
* Convert a string to propercase. ie This Is Proper Text
* @param input string to be formatted
* @return
*/
public static String toProperCase(String input) {
return WordUtils.capitalizeFully(input);
}
/**
* Convert a date into a field like "just then, 2 minutes ago, 4 hours ago, yesterday, on sunday, etc"
*
* @param date date to convert
*/
public static String convertDateForStatus(Date date) {
//current time
Calendar currentCal = Calendar.getInstance();
long currentTimeMillis = currentCal.getTimeInMillis();
//posting time
long postingTimeMillis = date.getTime();
//difference
int diff = (int)(currentTimeMillis - postingTimeMillis);
Locale locale = getUserPreferredLocale();
//System.out.println("currentDate:" + currentTimeMillis);
//System.out.println("postingDate:" + postingTimeMillis);
//System.out.println("diff:" + diff);
int MILLIS_IN_SECOND = 1000;
int MILLIS_IN_MINUTE = 1000 * 60;
int MILLIS_IN_HOUR = 1000 * 60 * 60;
int MILLIS_IN_DAY = 1000 * 60 * 60 * 24;
int MILLIS_IN_WEEK = 1000 * 60 * 60 * 24 * 7;
if(diff < MILLIS_IN_SECOND) {
//less than a second
return Messages.getString("Label.just_then");
} else if (diff < MILLIS_IN_MINUTE) {
//less than a minute, calc seconds
int numSeconds = diff/MILLIS_IN_SECOND;
if(numSeconds == 1) {
//one sec
return Messages.getString("Label.second_ago", new Object[] {numSeconds});
} else {
//more than one sec
return Messages.getString("Label.seconds_ago", new Object[] {numSeconds});
}
} else if (diff < MILLIS_IN_HOUR) {
//less than an hour, calc minutes
int numMinutes = diff/MILLIS_IN_MINUTE;
if(numMinutes == 1) {
//one minute
return Messages.getString("Label.minute_ago", new Object[] {numMinutes});
} else {
//more than one minute
return Messages.getString("Label.minutes_ago", new Object[] {numMinutes});
}
} else if (diff < MILLIS_IN_DAY) {
//less than a day, calc hours
int numHours = diff/MILLIS_IN_HOUR;
if(numHours == 1) {
//one hour
return Messages.getString("Label.hour_ago", new Object[] {numHours});
} else {
//more than one hour
return Messages.getString("Label.hours_ago", new Object[] {numHours});
}
} else if (diff < MILLIS_IN_WEEK) {
//less than a week, calculate days
int numDays = diff/MILLIS_IN_DAY;
//now calculate which day it was
if(numDays == 1) {
return Messages.getString("Label.yesterday");
} else {
//set calendar and get day of week
Calendar postingCal = Calendar.getInstance();
postingCal.setTimeInMillis(postingTimeMillis);
int postingDay = postingCal.get(Calendar.DAY_OF_WEEK);
//set to localised value: 'on Wednesday' for example
String dayName = getDayName(postingDay,locale);
if(dayName != null) {
return Messages.getString("Label.on", new Object[] {toProperCase(dayName)});
}
}
} else {
//over a week ago, we want it blank though.
}
return null;
}
/**
* Gets the users preferred locale, either from the user's session or Sakai preferences and returns it
* This depends on Sakai's ResourceLoader.
*
* @return
*/
public static Locale getUserPreferredLocale() {
ResourceLoader rl = new ResourceLoader();
return rl.getLocale();
}
/**
* Creates a full profile event reference for a given reference
* @param ref
* @return
*/
public static String createEventRef(String ref) {
return "/profile/"+ref;
}
/**
* Method for getting a value from a map based on the given key, but if it does not exist, use the given default
* @param map
* @param key
* @param defaultValue
* @return
*/
public static Object getValueFromMapOrDefault(Map<?,?> map, Object key, Object defaultValue) {
return (map.containsKey(key) ? map.get(key) : defaultValue);
}
/**
* Method to chop a String into it's parts based on the separator and return as a List. Useful for multi valued Sakai properties
* @param str the String to split
* @param separator separator character
* @return
*/
public static List<String> getListFromString(String str, char separator) {
String[] items = StringUtils.split(str, separator);
return Arrays.asList(items);
}
/**
* Processes HTML and escapes evils tags like <script>, also converts newlines to proper HTML breaks.
* @param s
* @return
*/
public static String processHtml(String s){
return FormattedText.processFormattedText(s, new StringBuilder(), true, false);
}
/**
* Strips string of HTML and returns plain text.
*
* @param s
* @return
*/
public static String stripHtml(String s) {
return FormattedText.convertFormattedTextToPlaintext(s);
}
/**
* Trims text to the given maximum number of displayed characters.
* Supports HTML and preserves formatting.
*
* @param s the string
* @param maxNumOfChars num chars to keep. If HTML, it's the number of content chars, ignoring tags.
* @param isHtml is the string HTML?
* @return
*/
public static String truncate(String s, int maxNumOfChars, boolean isHtml) {
if (StringUtils.isBlank(s)) {
return "";
}
//html
if(isHtml) {
StringBuilder trimmedHtml = new StringBuilder();
FormattedText.trimFormattedText(s, maxNumOfChars, trimmedHtml);
return trimmedHtml.toString();
}
//plain text
return StringUtils.substring(s, 0, maxNumOfChars);
}
/**
* Trims and abbreviates text to the given maximum number of displayed
* characters (less 3 characters, in case "..." must be appended).
* Supports HTML and preserves formatting.
*
* @param s the string
* @param maxNumOfChars num chars to keep. If HTML, it's the number of content chars, ignoring tags.
* @param isHtml is the string HTML?
* @return
*/
public static String truncateAndAbbreviate(String s, int maxNumOfChars, boolean isHtml) {
if (StringUtils.isBlank(s)) {
return "";
}
//html
if(isHtml) {
StringBuilder trimmedHtml = new StringBuilder();
boolean trimmed = FormattedText.trimFormattedText(s, maxNumOfChars - 3, trimmedHtml);
if (trimmed) {
int index = trimmedHtml.lastIndexOf("</");
if (-1 != index) {
trimmedHtml.insert(index, "...");
} else {
trimmedHtml.append("...");
}
}
return trimmedHtml.toString();
}
//plain text
return StringUtils.abbreviate(s, maxNumOfChars);
}
/**
* Generate a UUID
* @return
*/
public static String generateUuid() {
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
/**
* Returns the SkypeMe URL for the specified Skype username.
*
* @param skypeUsername
* @return the SkypeMe URL for the specified Skype username.
*/
public static String getSkypeMeURL(String skypeUsername) {
return "skype:" + skypeUsername + "?call";
}
/**
* Remove duplicates from a list, order is not retained.
*
* @param list list of objects to clean
*/
public static <T> void removeDuplicates(List<T> list){
Set<T> set = new HashSet<T>();
set.addAll(list);
list.clear();
list.addAll(set);
}
/**
* Remove duplicates from a list, order is retained.
*
* @param list list of objects to clean
*/
public static <T> void removeDuplicatesWithOrder(List<T> list) {
Set<T> set = new HashSet<T> ();
List<T> newList = new ArrayList<T>();
for(T e: list) {
if (set.add(e)) {
newList.add(e);
}
}
list.clear();
list.addAll(newList);
}
/**
* Determine how many iterations are needed to get through a list, in chunks
* @param total total number of items
* @param divisor amount per set
* @return number of iterations needed
*/
public static int getIterations(int total, int divisor){
BigDecimal[] d = new BigDecimal(total).divideAndRemainder(new BigDecimal(divisor));
//if no remainder, we had an exact value so only return the number
if(d[1].equals(BigDecimal.ZERO)){
return d[0].intValue();
}
//otherwise, return number required +1 (to account for remainder)
return d[0].intValue()+1;
}
}