利用编写XML文件和properties文件来实现窗口的生成,目的是为了减少重复量大且冗杂的代码,提高编程效率。通过对XML文件和Properties文件的解析,利用反射机制来调用相应方法。因为在swing窗口的代码实现时,对各种控件的设置和操作都是大同小异,重复率很高;通过反射机制来实现相关设置和操作就大大简化了代码的重复,各类控件的添加设置只需执行这一段代码。作为练习,此项目只完成swing的基本内容,尚不完善。
首先给出实现窗口所需要编写的XML文件:
编写窗口的XML文件:testView.XML。
下面是实现窗口的XML文件(仅用于测试),参数和方法名称的写法是自己规定的,便于在编程时去做判断,也便于XML文件编写者对方法名和参数类型进行区分(方法名前一律用 “=” ;参数一律用 “,” 分开;参数若是对象则在前方添加 “#”)。
注意:fatherId的存在是为了标明各个控件的从属关系,甚至是XML文件的父子关系(将字体、大小、颜色等只需完成一次设置的可另外做成一个父XML文件,在程序内部自动解析,在进行窗口编程时无需在对其进行设置)。
<?xml version="1.0" encoding="UTF-8"?>
<view name="view" fatherId="viewController">
<controller name="jmenubar" type="=jmenubar" para="" fatherId=""></controller>
<controller name="jframeMain" type="=jfrm" para="'我的窗口'" fatherId="">
<method para="600,500" name="=size"/>
<method para="300,200" name="=location"/>
<method para="#jmenubar" name="=setjmenubar"/>
</controller>
<controller name="jmenuOne" type="=jmenu" para="'文件'" fatherId="jmenubar"></controller>
<controller name="jmenuTwo" type="=jmenu" para="'编辑'" fatherId="jmenubar"></controller>
<controller name="jpnOne" type="=jpnl" para="" fatherId="jframeMain" layout="North">
<method para="#dimensionOne" name="=presize"/>
<method para="#colorBlue" name="=backGround"/>
</controller>
<controller name="jpnTwo" type="=jpnl" para="" fatherId="jframeMain" layout="Center">
<method para="#borderlayout" name="=setlayout"/>
<method para="#dimensionTwo" name="=presize"/>
<method para="#colorGreen" name="=backGround"/>
</controller>
<controller name="jlbTest" type="=jlbl" para="'标签'" fatherId="jpnOne">
<method para="100,100" name="=size"/>
<method para="0,0" name="=location"/>
<method para="#font" name="=setfont"/>
</controller>
<!--<controller name="confrim" type="=jbtn" para="'确定'" fatherId="jpnTwo">
<method para="#borderlayout" name="=setlayout"/>
<method para="#dimensionThree" name="=presize"/>
<method para="100,50" name="=size"/>
<method para="100,200" name="=location"/>
<method para="#font" name="=setfont"/>
</controller> -->
</view>
父XML文件:viewController.XML。
<?xml version="1.0" encoding="UTF-8"?>
<view name="viewController" fatherId="">
<controller name="colorBlue" type="=color" para="65,105,225" fatherId=""></controller>
<controller name="colorGreen" type="=color" para="0,255,0" fatherId=""></controller>
<controller name="borderlayout" type="=borderlayout" para="" fatherId=""></controller>
<controller name="font" type="=font" para="'宋体',1,30" fatherId=""></controller>
<controller name="dimensionOne" type="=dimension" para="600,100" fatherId=""></controller>
<controller name="dimensionTwo" type="=dimension" para="" fatherId=""></controller>
<controller name="dimensionThree" type="=dimension" para="100,50" fatherId=""></controller>
</view>
对类和方法调用时需要的Properties文件:view.properties。
内部源代码实现:
ParameterModel类:
首先给出ParameterModel类,做最基本的参数类型和参数值的处理。
1.将XML文件的值都作为参数去处理;
2.setPara方法:处理对象类型参数。因为存在XML文件的层次结构,所以对象型参数可能不会在当前XML文件中,如“#font”,所以需要查找该对象从属的类。采用递归是因为未来的编程可能会出现多层XML文件,做长远打算。
public class ParameterModel {
private Class<?> parameterType;
private Object parameterValue;
public ParameterModel() {
}
public Class<?> getParameterType() {
return parameterType;
}
public Object getParameterValue() {
return parameterValue;
}
void setPara(String para) {
if (para.startsWith("#")) {
ViewFactory viewFactory = ViewFactory.currentInstance();
parameterValue = getViewFactory(viewFactory, para.substring(1));
parameterType = parameterValue.getClass();
return;
}
setParameterType(para);
setParameterValue(para);
}
public void setParameterType(String para) {
parameterType = getOneParameterType(para);
}
public void setParameterValue(String para) {
parameterValue = getOneParameterValue(para, parameterType);
}
public Class<?> getOneParameterType(String para) {
if (para.startsWith("'") && para.endsWith("'")) {
return String.class;
}
if (para.equalsIgnoreCase("true") || para.equalsIgnoreCase("false") ) {
return boolean.class;
}
try {
Integer.valueOf(para);
return int.class;
} catch (NumberFormatException e) {
}
try {
Double.valueOf(para);
return double.class;
} catch (NumberFormatException e) {
}
return Object.class;
}
public Object getOneParameterValue(String para, Class<?> type) {
if (para.startsWith("=")) {
para = PropertiesParser.value(para).substring(1);
}
if (type.equals(String.class)) {
return para.substring(1, para.length() - 1);
}
if (type.equals(boolean.class)) {
return Boolean.valueOf(para);
}
if (type.equals(int.class)) {
return Integer.valueOf(para);
}
if (type.equals(double.class)) {
return Double.valueOf(para);
}
return null;
}
private Object getViewFactory(ViewFactory viewFactory, String name) {
Object object = viewFactory.getElement(name);
if (object == null) {
String father = viewFactory.getFather();
if (father == "") {
return null;
}
object = getViewFactory(XMLFactory.getElement(father), name);
}
return object;
}
}
Parameters类:
将同一层标签下的参数存储在list中,遍历得到每一个参数的值和类型。
package com.chy.myEasySwing.core;
import java.util.ArrayList;
import java.util.List;
public class Parameters {
private List<ParameterModel> parameterList;
public Parameters(String para) {
parameterList = new ArrayList<>();
String[] paras = para.split(",");
//处理无参构造
if (paras.length == 1 && paras[0].length() == 0) {
return;
}
for (int i = 0; i < paras.length; i++) {
ParameterModel parameter = new ParameterModel();
parameter.setPara(paras[i]);
parameterList.add(parameter);
}
}
Class<?>[] getParameterTypes() {
if (parameterList == null) {
return new Class<?>[] {};
}
int count = parameterList.size();
Class<?>[] parameterTypes = new Class<?>[count];
for (int index = 0; index < count; index++) {
parameterTypes[index] = parameterList.get(index).getParameterType();
}
return parameterTypes;
}
Object[] getParameterValues() {
if (parameterList == null) {
return new Object[] {};
}
int count = parameterList.size();
Object[] parameterValues = new Object[count];
for (int index = 0; index < count; index++) {
parameterValues[index] = parameterList.get(index).getParameterValue();
}
return parameterValues;
}
}
ViewFactory类:
将每一个控件(即XML文件中的controller)存储到Map中,将其中name属性的值作为键(也就是该控件名称)。一个ViewFactory对象相当于一个窗口。
package com.chy.myEasySwing.core;
import java.util.HashMap;
import java.util.Map;
public class ViewFactory {
private static ViewFactory viewFactory;
private Map<String, Object> viewMap;
private String father;
public String getFather() {
return father;
}
public void setFather(String father) {
this.father = father;
}
private ViewFactory() {
viewMap = new HashMap<>();
}
public static ViewFactory newInstance() {
viewFactory = new ViewFactory();
return viewFactory;
}
public static ViewFactory currentInstance() {
return viewFactory == null ? newInstance() : viewFactory;
}
void addElement(String name, Object object) {
viewMap.put(name, object);
}
@SuppressWarnings("unchecked")
public <T> T getElement(String name) {
return (T) viewMap.get(name);
}
}
ViewFactoryBulider类:
三层XML解析分别是父XML文件的解析、controller标签、method标签。
接下来就是解析文件和反射机制读取类、参数、方法。使用自己的XML和Properties解析工具完成对内容的获取,在利用反射机制得到类、方法、参数并执行。在这里出现了IAdd()接口,因为控件的添加最后都需要将其添加到指定父控件,并且涉及到布局模式就会有双参形式。而所有的控件都是Component类派生出来的对象,所以就建立这样一个接口,将每一个控件重新设置并继承这个接口,以及会出现例如Container的添加等其他情况在这个继承类中操作。我们只需要在ViewFactoryBulider类中,只需要判断layout属性的值就可以完成add方法,并通过fatherId来决定添加在指定控件中。
在方法的反射中需要考虑到得到的参数的类型可能会与得到的方法的类型不一致(不同的类下可能会有相同的方法),就需要将java内部提供的所有method的类遍历出来,并与得到的方法的类比较,直至找到类型相同的同名方法。
IAdd接口:
package com.chy.myEasySwing.core;
import java.awt.Component;
public interface IAdd {
Component addElement(Component component);
void addElement(Component component, Object object);
}
ViewFactoryBulider类:
package com.chy.myEasySwing.core;
import java.awt.Component;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.w3c.dom.Element;
import com.chy.myEasySwing.core.Parameters;
public class ViewFactoryBuilder {
static {
PropertiesParser.loadProperties("/view.properties");
}
public static XMLFactory xmlGather = XMLFactory.newIntance();
public static XMLFactory loadView(String path) {
ViewFactory view = ViewFactory.newInstance();
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String name = element.getAttribute("name");
String father = element.getAttribute("fatherId");
xmlGather.addElement(name, view);
view.setFather(father);
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String name = element.getAttribute("name");
String type = element.getAttribute("type");
String para = element.getAttribute("para");
String fatherId = element.getAttribute("fatherId");
String layout = element.getAttribute("layout");
type = PropertiesParser.value(type.substring(1));
try {
Class<?> klass = Class.forName(type);
Parameters paras = new Parameters(para);
Constructor<?> constructor =
klass.getConstructor(paras.getParameterTypes());
Object controller = constructor.newInstance(paras.getParameterValues());
view.addElement(name, controller);
if(fatherId != "") {
if (fatherId == "jmenubar") {
IAdd iaddMenu = view.getElement(fatherId);
iaddMenu.addElement(view.getElement(father));
}
IAdd iadd = view.getElement(fatherId);
if (view.getElement(fatherId) instanceof Component) {
if (layout != "") {
iadd.addElement(view.getElement(name), layout);
} else {
iadd.addElement(view.getElement(name));
}
}
}
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String name = element.getAttribute("name");
String para = element.getAttribute("para");
name = PropertiesParser.value(name.substring(1));
Parameters paras = new Parameters(para);
try {
Class<?>[] paraTypes = paras.getParameterTypes();
Object[] paraValues = paras.getParameterValues();
Method[] methods = klass.getMethods();
for(int subscript = 0; subscript < methods.length; subscript++) {
if (!methods[subscript].getName().equals(name)) {
continue;
}
Class<?>[] methodTypes = methods[subscript].getParameterTypes();
if (paraTypes.length == methods[subscript].getParameterCount()) {
int flag = 0;
for (int i = 0; i < methodTypes.length; i++) {
if (paraTypes[i].isAssignableFrom(methodTypes[i])) {
flag++;
}
}
if (flag == paraValues.length) {
methods[subscript].invoke(controller, paraValues);
break;
}
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}.dealElementInTag(element, "method");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}.dealElementInTag(element, "controller");
}
}.dealElementInTag(XMLParser.getDocument(path), "view");
return xmlGather;
}
}
XMLFactory类:
若后续需要做更为复杂或多个相关联的窗口,并且他们都会使用同一个父XML(如viewController.XML),需要将窗口(ViewFactory)作为Map的值,窗口所对应的XML文件的name作为键,存到Map中。为后续开发定下基本的逻辑。
package com.chy.myEasySwing.core;
import java.util.HashMap;
import java.util.Map;
public class XMLFactory {
private static XMLFactory xmlFactory;
private static Map<String, ViewFactory> xmlMap;
public XMLFactory() {
xmlMap = new HashMap<>();
}
public static XMLFactory newIntance() {
xmlFactory = new XMLFactory();
return xmlFactory;
}
public static XMLFactory currentInstance() {
return xmlFactory == null ? newIntance() : xmlFactory;
}
void addElement(String name, ViewFactory viewFactory) {
xmlMap.put(name, viewFactory);
}
@SuppressWarnings("unchecked")
public static <T> T getElement(String name) {
return (T) xmlMap.get(name);
}
}
(示例)MJFrame继承类:
package com.chy.myEasySwing.core;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.HeadlessException;
import javax.swing.JFrame;
public class MJFrame extends JFrame implements IAdd {
private static final long serialVersionUID = 2844635219692314818L;
private Container container = this.getContentPane();
public MJFrame() throws HeadlessException {
super();
this.container = getContentPane();
container.setLayout(new BorderLayout());
}
public MJFrame(String title) throws HeadlessException {
super(title);
}
@Override
public Component addElement(Component component) {
return container.add(component);
}
@Override
public void addElement(Component component, Object object) {
container.add(component, object);
}
}