常量:
package clock;
/**
* 常量
*
* @author chenlun
*
*/
public final class Constants {
/**
* 响铃频率
*/
public static final String[] frequencies = new String[] { "仅一次", "每天", "每周" };
public static final Integer[] hours = new Integer[24];
public static final Integer[] minutes = new Integer[60];
public static final String clockFile = "D:/MyDiary/clock/clocks.txt";
// eclipse里有用,打包后无效
// public static final String musicFile = "src/clock/music/Red.flac";
public static final String musicFile = "D:/MyDiary/clock/Red.flac";
public static final String logFile = "src/log4j.properties";
// public static final String readMeFile = "src/clock/readMe.txt";
public static final String readMeFile = "D:/MyDiary/clock/readMe.txt";
static {
for (int j = 0; j < 24; j++)
hours[j] = j;
for (int i = 0; i < 60; i++)
minutes[i] = i;
}
}
播放器:
package clock;
import java.io.IOException;
import java.util.Date;
import javax.sound.sampled.LineUnavailableException;
import org.apache.log4j.Logger;
import org.kc7bfi.jflac.apps.Player;
/**
* 播放jflac格式音乐文件,导入jflac包。播放主要步骤就2步
*
* @author chenlun
*
*/
public class FlacMusicPlayer {
private static final Logger logger = Logger.getLogger(FlacMusicPlayer.class);
private Thread t;
public void playMusic(String filename) {
t = new Thread(new Runnable() {
@Override
public void run() {
// 1
Player player = new Player();
try {
logger.info("响铃开始,时间为" + ThreadLocalDataFormat.getBasicDataFormat().format(new Date()));
// 2
player.decode(filename);
} catch (IOException | LineUnavailableException e) {
e.printStackTrace();
}
}
});
t.start();
}
@SuppressWarnings("deprecation")
public void pause() {
if (t != null) {
t.stop();
logger.info("手动关闭闹钟");
}
}
}
包装闹钟记录:
package clock;
import java.util.Date;
/**
* 日期格式:2019-01-01 00:00:00 每天/周
* 包装日期字符串,按顺序排
*
* @author chenlun
*
*/
public class ClockTime implements Comparable<ClockTime> {
String value;
public ClockTime(String value) {
this.value = value;
}
@Override
public int compareTo(ClockTime o) {
String time = value.split("\t")[0];
String time2 = o.value.split("\t")[0];
return compareInternal(time.trim(), time2.trim());
}
private int compareInternal(String s1, String s2) {
Date date1 = null, date2 = null;
date1 = ThreadLocalDataFormat.parse(s1);
date2 = ThreadLocalDataFormat.parse(s2);
if (s1.equals(s2)) {
return 0;
} else if (date1.before(date2)) {
return -1;
} else
return 1;
}
}
本地线程DateFormat:
package clock;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* SimpleDateFormat 非线程安全
*
* @author chenlun
*
*/
public class ThreadLocalDataFormat {
private static final ThreadLocal<SimpleDateFormat> dataFormat = new ThreadLocal<>();
public static SimpleDateFormat getBasicDataFormat() {
SimpleDateFormat sdf = dataFormat.get();
// 如果当前线程没有,新建一个实例,并设置到ThreadLocal
if (null == sdf) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dataFormat.set(sdf);
}
return sdf;
}
/**
* 将字符串转换成日期
* @param s
* @return
*/
public static Date parse(String s) {
Date date = null;
try {
date = getBasicDataFormat().parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
时间处理:
package clock;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class TimeUtil {
/**
* 将时分形式时间补全
* 如13:59——2019-01-01 13:59:00
*
* @param time
* @return
* @throws ParseException
*/
public static String fullTime(String time) throws ParseException {
String nowtime = ThreadLocalDataFormat.getBasicDataFormat().format(new Date());
int index = nowtime.indexOf(":");
return nowtime.substring(0, index - 2) + time + ":00";
}
/**
* 时间+频率
*
* @param hour
* @param minute
* @param frequencyIndex
* @return
* @throws ParseException
*/
public static String[] fullTimeWithFrequency(int hour, int minute, int frequencyIndex) throws ParseException {
String[] timeAndFrequency = new String[2];
timeAndFrequency[0] = fullTime(hour + ":" + minute);
timeAndFrequency[1] = Constants.frequencies[frequencyIndex];
return timeAndFrequency;
}
/**
* 明天的闹钟时刻
*
* @param time
* @return
* @throws ParseException
*/
public static String nextDayTime(String time) {
Date date=null;
try {
date = ThreadLocalDataFormat.getBasicDataFormat().parse(time);
} catch (ParseException e) {
e.printStackTrace();
}
Calendar cal = Calendar.getInstance(Locale.CHINA);
cal.setTime(date);
cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) + 1);
return ThreadLocalDataFormat.getBasicDataFormat().format(cal.getTime());
}
/**
* 下周的闹钟时刻
*
* @param time
* @return
* @throws ParseException
*/
public static String nextWeekTime(String time) {
Date date=null;
try {
date = ThreadLocalDataFormat.getBasicDataFormat().parse(time);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Calendar cal = Calendar.getInstance(Locale.CHINA);
cal.setTime(date);
cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) + 7);
return ThreadLocalDataFormat.getBasicDataFormat().format(cal.getTime());
}
/**
* 判断是否是今天
*
* @param string
* @return
* @throws ParseException
*/
public static boolean isToday(String string) {
Calendar cal = Calendar.getInstance(Locale.CHINA);
Date date=null;
try{
date=ThreadLocalDataFormat.getBasicDataFormat().parse(string);
}catch(ParseException e){
e.printStackTrace();
}
cal.setTime(date);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
cal.setTime(new Date());
int nowYear = cal.get(Calendar.YEAR);
int nowMonth = cal.get(Calendar.MONTH);
int nowDay = cal.get(Calendar.DAY_OF_MONTH);
return year == nowYear && month == nowMonth && day == nowDay;
}
}
调度类:
package clock;
import java.io.IOException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
/**
* 调度类
*
* @author chenlun
*
*/
public class ExecuteTask {
private static final Logger log = Logger.getLogger(ExecuteTask.class);
/**
* 停止响铃
*
* @param player
*/
public static void pause(FlacMusicPlayer player) {
player.pause();
}
/**
* 仅执行一次,不进行记录。这样闹钟文件里的都是每天或每周闹钟,每次修改过期时间即可
*
* @param executor
* @param player
* @param executeDate
*/
public static void executeOnce(ScheduledExecutorService executor, FlacMusicPlayer player, Date executeDate) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
player.playMusic(Constants.musicFile);
}
});
executor.schedule(t, executeDate.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/**
* 对于每天和每周事件
*
* @param executor
* @param player
* @param executeDate
* @param timeWithFrequency
*/
public static void execute(ScheduledExecutorService executor, FlacMusicPlayer player, Date executeDate,
String[] timeWithFrequency, Set<ClockTime> set) {
String time = timeWithFrequency[0];
String fre = timeWithFrequency[1];
Date date = new Date();
// 如果闹钟需要当天执行,则先执行
if (executeDate.after(date)) {
executeOnce(executor, player, executeDate);
}
// 不管当天是否需要执行,添加到set,同时(只)写入下一次闹钟发生时刻防止忘了保存
if (fre.equals("每天")) {
try {
set.add(new ClockTime(time + "\t" + Constants.frequencies[1] + "\r\n"));
IOUtil.write(Constants.clockFile, time + "\t" + Constants.frequencies[1] + "\r\n",
true);
} catch (IOException e) {
log.error("写入每天闹钟发生错误");
e.printStackTrace();
}
} else {
try {
set.add(new ClockTime(time + "\t" + Constants.frequencies[2] + "\r\n"));
IOUtil.write(Constants.clockFile,
time + "\t" + Constants.frequencies[2] + "\r\n", true);
} catch (IOException e) {
log.error("写入每周闹钟发生错误");
e.printStackTrace();
}
}
}
/**
* 读取文件到set,去除过期时间,执行当天未到时间闹钟任务
*
* @param set
* @param executor
* @param player
*/
public static void initSet(Set<ClockTime> set, ScheduledExecutorService executor, FlacMusicPlayer player) {
try {
// 读入文件
set = IOUtil.read(Constants.clockFile);
} catch (IOException e2) {
log.error("读入闹钟文件发生错误");
e2.printStackTrace();
}
Date tempDate = new Date();
// 先将今天过期的时间调整,未到的闹钟执行任务
if (!set.isEmpty())
for (ClockTime time : set) {
String[] taf = time.value.split("\t");
if (ThreadLocalDataFormat.parse(taf[0]).before(tempDate)) {
if (taf[1].equals(Constants.frequencies[1])) {
// 直到找到大于当前时间的闹钟
while (ThreadLocalDataFormat.parse(taf[0]).before(tempDate)) {
taf[0] = TimeUtil.nextDayTime(taf[0]);
}
time = new ClockTime(taf[0] + "\t" + taf[1]);
} else {
while (ThreadLocalDataFormat.parse(taf[0]).before(tempDate)) {
taf[0] = TimeUtil.nextWeekTime(taf[0]);
}
time = new ClockTime(taf[0] + "\t" + taf[1]);
}
}
// 大于当前时间则只找今天的闹钟,因为今天电脑要关机的。
// 如果是今天,且时间未到,建立任务执行
else {
if (TimeUtil.isToday(taf[0])) {
executeOnce(executor, player, ThreadLocalDataFormat.parse(taf[0]));
}
}
}
// 修改过期的时间,当天稍后过期的时间下一次清理。重新写入文件
try {
IOUtil.write(Constants.clockFile, set);
} catch (IOException e2) {
log.error("写入闹钟文件发生错误");
e2.printStackTrace();
}
}
}
I/O工具类:
package clock;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Set;
import java.util.TreeSet;
/**
* 读写工具
*
* @author chenlun
*
*/
public class IOUtil {
/**
* 非追加
*
* @param filename
* @param msg
* @throws IOException
*/
public static void write(String filename, String msg) throws IOException {
write(filename, msg, false);
}
/**
* txt在windows编码总是会改成gb2312等
*
* @param filename
* @param msg
* @param b
* 是否以追加形式写入
* @throws IOException
*/
public static void write(String filename, String msg, boolean b) throws IOException {
FileOutputStream fos;
if (b)
fos = new FileOutputStream(filename, true);
else
fos = new FileOutputStream(filename);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos, "gb2312"));
bw.write(msg);
bw.flush();
bw.close();
}
/**
* 第一行为标题:响铃时间——响铃频率
*
* @param filename
* @return
* @throws IOException
*/
public static Set<ClockTime> read(String filename) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "gb2312"));
Set<ClockTime> times = new TreeSet<>();
String len;
br.readLine();
while ((len = br.readLine()) != null) {
if (!len.trim().isEmpty())
times.add(new ClockTime(len));
}
br.close();
return times;
}
/**
* 读取readme文件
*
* @param filename
* @return
* @throws IOException
*/
public static String readMe(String filename) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "utf-8"));
StringBuilder sb = new StringBuilder();
String len;
while ((len = br.readLine()) != null) {
if (!len.trim().isEmpty())
sb.append(len + "\r\n");
}
br.close();
return sb.toString();
}
/**
* 快捷写入
*
* @param clockfile
* @param set
* @throws IOException
*/
public static void write(String clockfile, Set<ClockTime> set) throws IOException {
write(clockfile, "【响铃时间】\t【响铃频率】" + "\r\n");
for (ClockTime time : set) {
write(clockfile, time.value + "\r\n", true);
}
}
}
入口main方法所在类:
package clock;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
/**
* 删除后如何移除已被调度任务?
*
* @author chenlun
*
*/
public class MyClock {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
ClockFrame frame = new ClockFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
class ClockFrame extends JFrame {
private static final long serialVersionUID = 1L;
private final Logger log = Logger.getLogger(MyClock.class);
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private final FlacMusicPlayer player = new FlacMusicPlayer();
Set<ClockTime> set = new TreeSet<>();
JTextPane area;
final int WIDTH = 1000;
final int HEIGHT = 800;
JMenuBar menus = new JMenuBar();
JMenu file = new JMenu("菜单");
JMenuItem add = new JMenuItem("添加");
JMenuItem findAll = new JMenuItem("查看所有");
JMenuItem delete = new JMenuItem("删除");
JMenuItem save = new JMenuItem("保存");
JMenuItem stop = new JMenuItem("停止");
JMenuItem readMe = new JMenuItem("ReadMe");
public ClockFrame() {
super("clock-1.0");
PropertyConfigurator.configure(Constants.logFile);
file.setFont(new Font("楷体", Font.BOLD, 20));
menus.add(file);
file.add(add);
file.addSeparator();
file.add(findAll);
file.addSeparator();
file.add(delete);
file.addSeparator();
file.add(save);
file.addSeparator();
file.add(stop);
file.addSeparator();
file.add(readMe);
// 设置快捷操作
add.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_UP, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
delete.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
findAll.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
save.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
stop.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_P, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
readMe.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_M, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
area = new JTextPane();
area.setBackground(new Color(245, 255, 250));
area.setFont(new Font("楷体", Font.BOLD, 24));
JScrollPane scrollPane = new JScrollPane(area);// 滚动条
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int width = (int) screen.getWidth();
int height = (int) screen.getHeight();
// 左上角在屏幕的位置
setLocation(width / 4, height / 20);
setSize(WIDTH, HEIGHT);
// 添加完后设置菜单栏
setJMenuBar(menus);
getContentPane().add(scrollPane, BorderLayout.CENTER);
setResizable(true);
ExecuteTask.initSet(set, executor, player);
add.addActionListener(e -> {
int hour = (int) JOptionPane.showInputDialog(this, "选择时", "响铃时间", JOptionPane.PLAIN_MESSAGE, null,
Constants.hours, Constants.hours[12]);
int minute = (int) JOptionPane.showInputDialog(this, "选择分", "响铃时间", JOptionPane.PLAIN_MESSAGE, null,
Constants.minutes, Constants.minutes[30]);
int index = JOptionPane.showOptionDialog(this, "选择频率", "响铃频率", JOptionPane.OK_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, Constants.frequencies, Constants.frequencies[0]);
String[] timeWithFrequency = null;
try {
timeWithFrequency = TimeUtil.fullTimeWithFrequency(hour, minute, index);
Date executeDate = ThreadLocalDataFormat.parse(TimeUtil.fullTime(hour + ":" + minute));
// 如果只执行一次
if (index == 0) {
// 且时间已经过了
if (executeDate.before(new Date())) {
// do nothing
} else {
ExecuteTask.executeOnce(executor, player, executeDate);
JOptionPane.showMessageDialog(this, "闹钟已添加");
}
}
// 每天或每周的
else {
ExecuteTask.execute(executor, player, executeDate, timeWithFrequency, set);
JOptionPane.showMessageDialog(this, "闹钟已添加");
log.info("添加闹钟成功,时间为:" + timeWithFrequency[0] + "--" + timeWithFrequency[1]);
}
} catch (ParseException e1) {
log.error("添加闹钟发生错误");
e1.printStackTrace();
}
});
delete.addActionListener(e -> {
//空无操作
if(set.isEmpty()){
JOptionPane.showMessageDialog(this, "当前无闹钟");
}
else{
List<String> list = new ArrayList<>();
for (ClockTime time : set) {
list.add(time.value);
}
String time2 = (String) JOptionPane.showInputDialog(this, "闹钟时间", "删除闹钟", JOptionPane.INFORMATION_MESSAGE,
null, list.toArray(), list.get(0));
// 移除set中的
for (ClockTime time : set) {
if (time.value.equals(time2)) {
set.remove(time);
break;
}
}
// 保存改变,但是已经调度的任务如何取消?或者如何重启?
try {
IOUtil.write(Constants.clockFile, set);
} catch (IOException e1) {
e1.printStackTrace();
}
JOptionPane.showMessageDialog(this, "删除成功");
log.info("删除闹钟:" + time2);
}});
findAll.addActionListener(e -> {
StringBuilder sb = new StringBuilder();
for (ClockTime time : set) {
sb.append(time.value + "\r\n");
}
area.setText(sb.toString());
});
save.addActionListener(e -> {
try {
IOUtil.write(Constants.clockFile, set);
} catch (IOException e1) {
log.error("保存文件发生错误");
e1.printStackTrace();
}
});
stop.addActionListener(e -> {
ExecuteTask.pause(player);
});
readMe.addActionListener(e -> {
try {
String readme = IOUtil.readMe(Constants.readMeFile);
area.setText(readme);
} catch (IOException e1) {
log.error("读取readme文件发生错误");
e1.printStackTrace();
}
});
}
}
最后疑问:不知道该咋样取消已被调度的任务,重新赋值frame新实例也无法关闭前一个窗口。