常量:
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新實例也無法關閉前一個窗口。