有感于 XCode 代码模板机制,写了一个小工具,特此写博文以志。
模板的概念很简单,就是能拿来重复使用的东西。
在编程语言的范畴里,就是一份源代码文件,
里面有一些特定的字符串你给替换成其他的字符串以后,
边成为了一份新的、具有实际运用价值的代码文件。
代码模板文件仅仅是一具空壳子,
要让它真正的活起来,就要在里面插入具有实际操作效果的方法、逻辑。
我用 Java,一个很常用的模式就是:
将待处理的文件拽入 GUI 获取文件的路径,然后对该文件进行处理。
我觉得这样很方便,以前刚开始弄 java swing 的时候,
选个文件还得打开一个 JFileChooser 文件选择对话框,那是各种麻烦和蛋疼,
程序编写方面没有 “拖拽取文件路径” 简洁,实际使用起来也没有后者简洁~
这么一来,完全可以抛弃 JFileChooser 这个控件了,谁爱用谁用去,反正我是不会再用了~
另外,程序 ui 重用,派生子类重写父类的某些方法以扩展功能等,
都可以用模板机制来降低书写重复代码的工作量。
用实际源码来举例子吧:
Gui.java
package org.bruce.vertices.asist.common; import java.awt.Toolkit; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import javax.swing.JFrame; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; /** * @author Bruce Yang * 程序 ui~ */ public class Gui extends JFrame { private static final long serialVersionUID = -7044615012246731094L; public static final int JFRAME_WIDTH = 500; public static final int JFRAME_HEIGHT = 400; /** * @param title “单元处理器”的名称 * @param dtl DropTargetListener 实例~ */ public Gui(String title, Dta dtl) { this.setTitle(title); this.setSize(JFRAME_WIDTH, JFRAME_HEIGHT); JScrollPane jsp = new JScrollPane(); JTextArea jta = new JTextArea(); jsp.setViewportView(jta); this.add(jsp); JScrollBar jsb = jsp.getVerticalScrollBar(); jsb.setValue(jsb.getMaximum()); int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width; int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; this.setLocation((screenWidth-JFRAME_WIDTH)/2, (screenHeight-JFRAME_HEIGHT)/2); /** 关闭的时候将修改过的属性覆盖到 jar 里面的属性配置文件~ */ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); new DropTarget(jta, DnDConstants.ACTION_COPY_OR_MOVE, dtl); this.setVisible(true); } }Gui.java 是 “Drag && Drop” 类工具程序的界面窗口
接收两个参数,一个是窗口的标题栏文字,另一个是一个 DropTargetListener 的实现对象~
标题栏文字的作用在于提示用户该窗口用来完成什么功能,
实现 DropTargetListener 接口的对象封装了对拽入文件进行如何处理的逻辑。
Dta.java
package org.bruce.vertices.asist.common; import java.awt.datatransfer.DataFlavor; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetAdapter; import java.awt.dnd.DropTargetDropEvent; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.bruce.vertices.asist.utils.Functions; /** * @author Bruce Yang * 拖拽监听~ */ public abstract class Dta extends DropTargetAdapter { /** * @param f */ protected abstract void handle(File f); /* (non-Javadoc) * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent) */ @SuppressWarnings("unchecked") public void drop(DropTargetDropEvent event) { if (event.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); DataFlavor df = DataFlavor.javaFileListFlavor; List<File> list = null; try { list = (List<File>)(event.getTransferable().getTransferData(df)); } catch (Exception e) { e.printStackTrace(); } Iterator<File> iterator = list.iterator(); while (iterator.hasNext()) { File item = iterator.next(); List<File> fileList = new ArrayList<File>(); if(item.isDirectory()) { Functions.listFilesOnly(item, fileList); } else { fileList.add(item); } for(File f : fileList) { handle(f); } } event.dropComplete(true); } else { event.rejectDrop(); } } }Dta.java 是 DropTargetAdapter 类的子类,用来为 Gui 类的 JTextArea 控件增加拖拽取文件路径的功能。
同时,Dta 是一个抽象类,它包含了一个 handle() 抽象方法,这个方法将留给 Dta 的子类来重写。
至此,也就到了本文的重点内容了。
Dta 的子类只要重写了 handle() 抽象方法,就能够实现不同的文件处理功能。
下面给出几个例子:
1。包含打印出拽入文件(可以是多个文件)路径功能的子类实现:
PrintAbsPath.java
package org.bruce.vertices.asist.units; import java.io.File; import org.bruce.vertices.asist.common.Dta; import org.bruce.vertices.asist.common.Gui; /** * @author Bruce Yang * 这么一来就清晰多了~ */ public class PrintAbsPath extends Dta { @Override public void handle(File f) { // TODO Auto-generated method stub System.out.println(f.getAbsolutePath()); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Gui("PrintAbsPath", new PrintAbsPath()); } }
2。改变拽入文件中字符编码格式的子类实现
ConvertEncoding.java
package org.bruce.vertices.asist.units; import java.io.File; import org.bruce.vertices.asist.common.Dta; import org.bruce.vertices.asist.common.Gui; import org.bruce.vertices.asist.utils.StringFileBridge; /** * @author Bruce Yang * 改变文件的字符编码(GBK -> UTF-8)~ */ public class ConvertEncoding extends Dta { private static final String FROM = "GBK"; private static final String TO = "UTF-8"; @Override protected void handle(File f) { // TODO Auto-generated method stub String rawStr = StringFileBridge.file2String(f, FROM); String convertedStr = StringFileBridge.changeEncode(rawStr, TO); StringFileBridge.string2File(convertedStr, f); } /** * @param args */ public static void main(String[] args) { new Gui("ConvertEncoding", new ConvertEncoding()); } }
3。将拽入文件中数据进行 Base64 编码的子类实现
Base64Encrypt.java
package org.bruce.vertices.asist.units; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import org.bruce.vertices.asist.common.Dta; import org.bruce.vertices.asist.common.Gui; import org.bruce.vertices.asist.security.Base64; import org.bruce.vertices.asist.utils.Functions; import org.bruce.vertices.asist.utils.StringFileBridge; /** * @author Bruce Yang * 将文件内容以 base64 编码~ */ public class Base64Encrypt extends Dta { @Override protected void handle(File f) { // TODO Auto-generated method stub FileInputStream fis = null; try { fis = new FileInputStream(f); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } byte[] bytesData = Functions.inputStream2byteArray(fis); String encodedStr = Base64.encode(bytesData); StringFileBridge.string2File(encodedStr, f); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Gui("Base64Encrypt", new Base64Encrypt()); } }如上,这些个可爱的小窗口都包含了一定的功能,而你所要做的,
仅仅是在重写的 handle() 方法里面书写核心逻辑,
其他任何不必要的或是不经常要做修改的,都能“眼不见心不烦”,
这样的主体结构,是否能算的上比较优秀了?
从此,Gui,要多次被用到的类主体框架结构,拖拽去路径功能,
都与提供功能的核心逻辑分隔开了,
再也不用重复的去写类似
class MyJFramne extends JFrame ...
JFrame jf = new JFrame...
class MyDropTargetAdapter extends DropTargetAdapter...
的代码了。
世界,真美好~
好了,世界虽然美好了一点儿,但还是无法挡住我追求更美好世界的心。
如果你仔细观察,肯定也发现了,每一个 Dta 子类实现中都包含了一个程序入口方法:
public static void main(String[] args) {....
毫无疑问,对比与 c++ 的 void main(), int main(),
java的 public static void main(String[] args) { 给我带来过太多的惊喜,
我曾为每个类下面都能放一个这样的入口方法而感到心旷神怡,
但是如今,写了n次这般,铁哥们儿也给熬成婆了。
于是,我便想,如果能只写一次的话,那便太幸福了~
ok,至此模板策略便要出台了。有了模板,从此便可以这个该死的 public static void main(String[] args)...
说简单其实也很简单,就是弄一个通用的文件作为模板,如下:
Dta.txt
package org.bruce.vertices.asist.units; import java.io.File; import org.bruce.vertices.asist.common.Dta; import org.bruce.vertices.asist.common.Gui; import org.bruce.vertices.asist.utils.StringFileBridge; /** * @author Bruce Yang * */ public class _CLASS_NAME_ extends Dta { @Override protected void handle(File f) { // TODO Auto-generated method stub // 将文件内容读取为待处理的字符串~ String raw = StringFileBridge.file2String(f, "UTF-8"); // Add File Contents Handle-Logic below~ // 将处理完毕获得的字符串写回原文件~ StringFileBridge.string2File("", f); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Gui("_CLASS_NAME_", new _CLASS_NAME_()); } }接着,弄一个程序界面,在界面里面加入一个 JTextField 控件,
本例比较简单,如上面的模板文件 Dta.txt,
我要做的仅仅是将 _CLASS_NAME_ 替换为实际的类名即可。
那么,程序界面仅仅需要包含简单的一些逻辑即可:
1。将模板文件中的数据读成 java String 对象
2。对 String 对象做字符替换处理(如将 _CLASS_NAME_ 悉数替换为 EncodingConvertor)
3。将替换完毕后的 String 对象写入系统剪贴板
4。选中目的包,ctl + v 或 command + v 将剪贴板中的代码变成 类文件
(我用的是 eclipse,eclipse毫无疑问是具有这般神通的)
是不是很简单,这样的话,只需要在生成的类文件的 handle() 方法中插入最最核心的代码逻辑即可了。
如果没有这个小工具的话,至少要经过这么一些步骤(eclipse IDE):
1。选中目的包新建类
2。填写 Dta 子类的名称
3。浏览选中所要继承的父类 Dta,勾选生成主方法单选框。
点击 finish,至此子类的 java 文件就生成好了,
5。public static void main() 方法中的 new Gui("类名", new 类名());
这行是必须要通过手来敲的!!!
千篇一律的,很有规律,很消磨人对程序编写的热情,仅此而已~
恩,至此要说的差不多已经说完了,下面把其他两个类文件的代码贴出来:
TemplateGenerator.java
package org.bruce.vertices.asist.template; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; /** * @author Bruce Yang * 主要用于生成模板,只要填入 “单元处理器” 的名称即可将代码模板生成好并置入剪切板。 * 这样的话,只要新建一个类,统一好所使用的名称,将剪切板里面的代码模板复制到编辑器中就可以了~ */ public class TemplateGenerator extends JFrame { private static final long serialVersionUID = 8510818188986391801L; public static final int JFRAME_WIDTH = 500; public static final int JFRAME_HEIGHT = 400; // 用来做中转站将文本转移到系统剪贴板里面~ /** * 2012.05.31.19.32,jtf做中转站还是不行,将换行符给丢失掉了,蛋疼~ */ // JTextField jtf_not_visible = null; JTextField jtf = null; JButton jb = null; JTextArea jta = null; public TemplateGenerator() { // 1.标题~ this.setTitle("TemplateGenerator"); // 2。布局管理器~ this.setLayout(new BorderLayout()); // 3。置入 ui 控件~ // ============================================ // jtf_not_visible = new JTextField(); JPanel jp = new JPanel(); jp.setLayout(new GridLayout(1, 5)); // JPanel jp_sub = new JPanel(); // jp_sub.setLayout(new GridLayout(1, 2)); // JLabel jlb = new JLabel(" 模板类名:"); // jtf = new JTextField(); // jp_sub.add(jlb); // jp_sub.add(jtf); // jp.add(jp_sub); JLabel jlb = new JLabel(spacesGenerator(10) + "模板类名:"); jtf = new JTextField(); jp.add(jlb); jp.add(jtf); jb = new JButton(); jb.setText("生成"); jb.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { // TODO Auto-generated method stub String className = jtf.getText(); String tContents = TemplateLoader.loadTemplateByName("Dta"); // System.out.println(tContents); tContents = tContents.replaceAll("_CLASS_NAME_", className); System.out.println(tContents); // String tPreview = tContents.replaceAll("\t", spacesGenerator(8)); if(tContents != null && !tContents.equals("")) { // jta.setText(tPreview); // jtf_not_visible.setText(tContents); // 只有被选中的文字才会被弄进系统剪切板,少写这一行,花了我半天时间~ // jtf_not_visible.selectAll(); // jtf_not_visible.cut(); // jtf_not_visible.setText(""); /** 直接操作系统剪贴板,摈弃 JTextField 中转站~ */ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable tData = new StringSelection(tContents); clipboard.setContents(tData, null); } } }); jp.add(jb); // 占位标签控件(无他用,纯粹占位控制其他控件的排布格式)~ JLabel jlb_placeholder0 = new JLabel(""); jp.add(jlb_placeholder0); // JLabel jlb_placeholder1 = new JLabel(""); // jp.add(jlb_placeholder1); this.add(jp, BorderLayout.NORTH); JScrollPane jsp = new JScrollPane(); jta = new JTextArea(); jta.setEditable(false); jsp.setViewportView(jta); this.add(jsp, BorderLayout.CENTER); JScrollBar jsb = jsp.getVerticalScrollBar(); jsb.setValue(jsb.getMaximum()); // ============================================ // 4。设置窗体大小和窗口位置~ this.setSize(JFRAME_WIDTH, JFRAME_HEIGHT); int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width; int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; this.setLocation((screenWidth-JFRAME_WIDTH)/2, (screenHeight-JFRAME_HEIGHT)/2); /** 关闭的时候将修改过的属性覆盖到 jar 里面的属性配置文件~ */ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } /** * 生成由一定数目的空格符连接而成的字符串前缀~ * @param count * @return */ private String spacesGenerator(int count) { StringBuffer sb = new StringBuffer(); for(int i = 0; i < count; ++ i) { sb.append(' '); } return sb.toString(); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new TemplateGenerator(); } }TemplateLoader.java
package org.bruce.vertices.asist.template; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; /** * @author Bruce Yang * 用于将模板文件中数据加载为供程序使用的字符串对象~ */ public class TemplateLoader { public static final String CLASS_PATH = "/org/bruce/vertices/asist/template/"; /** * 按模板文件名称加载~ * @param tName * @return */ public static String loadTemplateByName(String tName) { String tFileClassPath = CLASS_PATH + tName + ".txt"; InputStream is = TemplateLoader.class.getResourceAsStream(tFileClassPath); byte[] bytes = inputStream2byteArray(is); String tContents = null; try { tContents = new String(bytes, "utf-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return tContents; } /** * InputStream 转 byte[]~ * @param is * @return */ public static byte[] inputStream2byteArray(InputStream is) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int i; try { while((i = is.read()) != -1) { baos.write(i); } baos.close(); } catch (IOException e) { e.printStackTrace(); } byte[] bytes = baos.toByteArray(); return bytes; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub String str = loadTemplateByName("Dta"); System.out.println(str); } }收工做饭~