|
|
@ -13,6 +13,7 @@ import com.fr.function.SUM; |
|
|
|
import com.fr.function.TIME; |
|
|
|
import com.fr.function.TIME; |
|
|
|
import com.fr.general.ComparatorUtils; |
|
|
|
import com.fr.general.ComparatorUtils; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.fr.log.FineLoggerFactory; |
|
|
|
import com.fr.plugin.ExtraClassManager; |
|
|
|
import com.fr.plugin.ExtraClassManager; |
|
|
|
import com.fr.stable.EncodeConstants; |
|
|
|
import com.fr.stable.EncodeConstants; |
|
|
|
import com.fr.stable.OperatingSystem; |
|
|
|
import com.fr.stable.OperatingSystem; |
|
|
@ -34,13 +35,75 @@ import java.util.zip.ZipFile; |
|
|
|
|
|
|
|
|
|
|
|
import javax.swing.DefaultListModel; |
|
|
|
import javax.swing.DefaultListModel; |
|
|
|
|
|
|
|
|
|
|
|
public abstract class FunctionConstants { |
|
|
|
public final class FunctionConstants { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static FunctionGroup PLUGIN = getPluginFunctionGroup(); |
|
|
|
|
|
|
|
public static FunctionGroup CUSTOM = getCustomFunctionGroup(); |
|
|
|
|
|
|
|
static NameAndFunctionList COMMON = getCommonFunctionList(); |
|
|
|
|
|
|
|
static NameAndTypeAndFunctionList[] EMBFUNCTIONS = getEmbededFunctionListArray(); |
|
|
|
|
|
|
|
public static FunctionGroup ALL = getAllFunctionGroup(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static { |
|
|
|
|
|
|
|
loadEmbededFunctions(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Don't let anyone instantiate this class. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private FunctionConstants() {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void loadEmbededFunctions() { |
|
|
|
|
|
|
|
String pkgName = "com.fr.function"; |
|
|
|
|
|
|
|
Class<Function> iface = Function.class; |
|
|
|
|
|
|
|
ClassLoader classloader = iface.getClassLoader(); |
|
|
|
|
|
|
|
Enumeration<URL> urlEnumeration = null; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
urlEnumeration = classloader.getResources(pkgName.replace('.', '/')); |
|
|
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
|
|
FineLoggerFactory.getLogger().error(e.getMessage()); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
while (urlEnumeration.hasMoreElements()) { |
|
|
|
|
|
|
|
URL url = urlEnumeration.nextElement(); |
|
|
|
|
|
|
|
String classFilePath = url.getFile(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:url.getFile获取的地址中,如果有空格或中文会被URLEncoder.encode处理 |
|
|
|
|
|
|
|
* 会变成%20这种%打头的东西,但是new File的时候%20是无法解析成空格,所以在此需要做URLDecoder.decode处理 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
classFilePath = URLDecoder.decode(classFilePath, EncodeConstants.ENCODING_UTF_8); |
|
|
|
|
|
|
|
} catch (UnsupportedEncodingException e1) { |
|
|
|
|
|
|
|
FRContext.getLogger().error(e1.getMessage(), e1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
FRContext.getLogger().info("ClassFilePath:" + classFilePath); |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:如果是jar包中的class文件 |
|
|
|
|
|
|
|
* file:/D:/opt/FineReport6.5/WebReport/WEB-INF/lib/fr-server-6.5.jar!/com/fr/rpt/script/function |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
for (String fileName : findClassNamesUnderFilePath(classFilePath)) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Class<?> cls = Class.forName(pkgName + "." + fileName.substring(0, fileName.length() - 6)); |
|
|
|
|
|
|
|
if (StableUtils.classInstanceOf(cls, iface)) { |
|
|
|
|
|
|
|
Function inst; |
|
|
|
|
|
|
|
inst = (Function)cls.newInstance(); |
|
|
|
|
|
|
|
for (NameAndTypeAndFunctionList EMBFUNCTION : EMBFUNCTIONS) { |
|
|
|
|
|
|
|
if (EMBFUNCTION.test(inst)) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ignore) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 将函数分组插件中的函数添加到对应的列表中 |
|
|
|
* 将函数分组插件中的函数添加到对应的列表中 |
|
|
|
* @param listModel |
|
|
|
* @param listModel |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static void addFunctionGroupFromPlugins(DefaultListModel listModel){ |
|
|
|
static void addFunctionGroupFromPlugins(DefaultListModel listModel){ |
|
|
|
//hugh:自定义函数分组
|
|
|
|
//hugh:自定义函数分组
|
|
|
|
Set<Mutable> containers = ExtraClassManager.getInstance().getArray(FunctionDefContainer.MARK_STRING); |
|
|
|
Set<Mutable> containers = ExtraClassManager.getInstance().getArray(FunctionDefContainer.MARK_STRING); |
|
|
|
if(!containers.isEmpty()){ |
|
|
|
if(!containers.isEmpty()){ |
|
|
@ -75,7 +138,63 @@ public abstract class FunctionConstants { |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static FunctionGroup PLUGIN = new FunctionGroup() { |
|
|
|
|
|
|
|
|
|
|
|
private static String[] findClassNamesUnderFilePath(String filePath) { |
|
|
|
|
|
|
|
java.util.List<String> classNameList = new ArrayList<String>(); |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:如果是jar包中的class文件 |
|
|
|
|
|
|
|
* file:/D:/opt/FineReport6.5/WebReport/WEB-INF/lib/fr-server-6.5.jar!/com/fr/rpt/script/function |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
if (filePath.contains("!/")) { |
|
|
|
|
|
|
|
String[] arr = filePath.split("!/"); |
|
|
|
|
|
|
|
String jarPath = arr[0].substring(6); // alex:substring(6)去掉前面的file:/这六个字符
|
|
|
|
|
|
|
|
String classPath = arr[1]; |
|
|
|
|
|
|
|
if(classPath.endsWith("/")){ |
|
|
|
|
|
|
|
classPath = classPath.substring(0, classPath.length() - 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!OperatingSystem.isWindows()){ |
|
|
|
|
|
|
|
//windows里substring后是d:\123\456, mac下substring后是Application/123/456
|
|
|
|
|
|
|
|
jarPath = StringUtils.perfectStart(jarPath, "/"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ZipFile zip; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
zip = new ZipFile(jarPath); |
|
|
|
|
|
|
|
Enumeration entries = zip.entries(); |
|
|
|
|
|
|
|
while (entries.hasMoreElements()) { |
|
|
|
|
|
|
|
ZipEntry entry = (ZipEntry) entries.nextElement(); |
|
|
|
|
|
|
|
if (entry.isDirectory()) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String entryName = entry.getName(); |
|
|
|
|
|
|
|
if (!entryName.contains(classPath) || !entryName.endsWith(".class")) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
classNameList.add(entryName.substring(classPath.length() + 1)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
|
|
FRContext.getLogger().error(e.getMessage(), e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
File dir = new File(filePath); |
|
|
|
|
|
|
|
File[] files = dir.listFiles(); |
|
|
|
|
|
|
|
if (files != null) { |
|
|
|
|
|
|
|
for (File f : files) { |
|
|
|
|
|
|
|
String fileName = f.getName(); |
|
|
|
|
|
|
|
if (fileName.endsWith(".class")) { |
|
|
|
|
|
|
|
classNameList.add(fileName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return classNameList.toArray(new String[0]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static FunctionGroup getPluginFunctionGroup() { |
|
|
|
|
|
|
|
return new FunctionGroup() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getGroupName() { |
|
|
|
public String getGroupName() { |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Base_Formula_Plugin"); |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Base_Formula_Plugin"); |
|
|
@ -92,8 +211,10 @@ public abstract class FunctionConstants { |
|
|
|
return nads; |
|
|
|
return nads; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static FunctionGroup CUSTOM = new FunctionGroup() { |
|
|
|
private static FunctionGroup getCustomFunctionGroup() { |
|
|
|
|
|
|
|
return new FunctionGroup() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getGroupName() { |
|
|
|
public String getGroupName() { |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Custom_Function"); |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Custom_Function"); |
|
|
@ -116,12 +237,16 @@ public abstract class FunctionConstants { |
|
|
|
return new NameAndDescription[0]; |
|
|
|
return new NameAndDescription[0]; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static NameAndFunctionList COMMON = new NameAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Most_Recently_Used"), new Function[] { |
|
|
|
private static NameAndFunctionList getCommonFunctionList() { |
|
|
|
|
|
|
|
return new NameAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Most_Recently_Used"), new Function[] { |
|
|
|
new SUM(), new COUNT(), new AVERAGE(), new CHAR(), new DATE(), new MAX(), new MIN(), new TIME(), new RANGE() |
|
|
|
new SUM(), new COUNT(), new AVERAGE(), new CHAR(), new DATE(), new MAX(), new MIN(), new TIME(), new RANGE() |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static NameAndTypeAndFunctionList[] EMBFUNCTIONS = new NameAndTypeAndFunctionList[] { |
|
|
|
private static NameAndTypeAndFunctionList[] getEmbededFunctionListArray() { |
|
|
|
|
|
|
|
return new NameAndTypeAndFunctionList[] { |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Math_&_Trig"), Function.MATH), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Math_&_Trig"), Function.MATH), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Text"), Function.TEXT), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Text"), Function.TEXT), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Date_&_Time"), Function.DATETIME), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Date_&_Time"), Function.DATETIME), |
|
|
@ -131,8 +256,10 @@ public abstract class FunctionConstants { |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Other"), Function.OTHER), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_Other"), Function.OTHER), |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Function_Type_Hierarchy"), Function.HA) |
|
|
|
new NameAndTypeAndFunctionList(com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_Function_Type_Hierarchy"), Function.HA) |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static FunctionGroup ALL = new FunctionGroup() { |
|
|
|
private static FunctionGroup getAllFunctionGroup() { |
|
|
|
|
|
|
|
return new FunctionGroup() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getGroupName() { |
|
|
|
public String getGroupName() { |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_All"); |
|
|
|
return com.fr.design.i18n.Toolkit.i18nText("Fine-Design_Basic_FormulaD_All"); |
|
|
@ -153,112 +280,16 @@ public abstract class FunctionConstants { |
|
|
|
Collections.addAll(all,createFunctionGroup(((FunctionDefContainer)container)).getDescriptions()); |
|
|
|
Collections.addAll(all,createFunctionGroup(((FunctionDefContainer)container)).getDescriptions()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
java.util.Collections.sort(all, NameAndDescriptionComparator); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return all.toArray(new NameAndDescription[all.size()]); |
|
|
|
Collections.sort(all, new Comparator<NameAndDescription>() { |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Comparator<NameAndDescription> NameAndDescriptionComparator = new Comparator<NameAndDescription>() { |
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public int compare(NameAndDescription o1, NameAndDescription o2) { |
|
|
|
public int compare(NameAndDescription o1, NameAndDescription o2) { |
|
|
|
return ComparatorUtils.compare(o1.getName(), o2.getName()); |
|
|
|
return ComparatorUtils.compare(o1.getName(), o2.getName()); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
private static String[] findClassNamesUnderFilePath(String filePath) { |
|
|
|
|
|
|
|
java.util.List<String> classNameList = new ArrayList<String>(); |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:如果是jar包中的class文件 |
|
|
|
|
|
|
|
* file:/D:/opt/FineReport6.5/WebReport/WEB-INF/lib/fr-server-6.5.jar!/com/fr/rpt/script/function |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
if (filePath.indexOf("!/") >= 0) { |
|
|
|
|
|
|
|
String[] arr = filePath.split("!/"); |
|
|
|
|
|
|
|
String jarPath = arr[0].substring(6); // alex:substring(6)去掉前面的file:/这六个字符
|
|
|
|
|
|
|
|
String classPath = arr[1]; |
|
|
|
|
|
|
|
if(classPath.endsWith("/")){ |
|
|
|
|
|
|
|
classPath = classPath.substring(0, classPath.length() - 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!OperatingSystem.isWindows()){ |
|
|
|
|
|
|
|
//windows里substring后是d:\123\456, mac下substring后是Application/123/456
|
|
|
|
|
|
|
|
jarPath = StringUtils.perfectStart(jarPath, "/"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ZipFile zip; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
zip = new ZipFile(jarPath); |
|
|
|
|
|
|
|
Enumeration entries = zip.entries(); |
|
|
|
|
|
|
|
while (entries.hasMoreElements()) { |
|
|
|
|
|
|
|
ZipEntry entry = (ZipEntry) entries.nextElement(); |
|
|
|
|
|
|
|
if (entry.isDirectory()) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String entryName = entry.getName(); |
|
|
|
|
|
|
|
if (entryName.indexOf(classPath) < 0 || !entryName.endsWith(".class")) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
classNameList.add(entryName.substring(classPath.length() + 1)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
|
|
FRContext.getLogger().error(e.getMessage(), e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
File dir = new File(filePath); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (File f : dir.listFiles()) { |
|
|
|
|
|
|
|
String fileName = f.getName(); |
|
|
|
|
|
|
|
if (fileName.endsWith(".class")) { |
|
|
|
|
|
|
|
classNameList.add(fileName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return classNameList.toArray(new String[classNameList.size()]); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// alex:读取com.fr.script.function包下面所有的Function类
|
|
|
|
|
|
|
|
static { |
|
|
|
|
|
|
|
String pkgName = "com.fr.function"; |
|
|
|
|
|
|
|
Class<Function> iface = Function.class; |
|
|
|
|
|
|
|
ClassLoader classloader = iface.getClassLoader(); |
|
|
|
|
|
|
|
URL url = classloader.getResource(pkgName.replace('.', '/')); |
|
|
|
|
|
|
|
String classFilePath = url.getFile(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:url.getFile获取的地址中,如果有空格或中文会被URLEncoder.encode处理 |
|
|
|
|
|
|
|
* 会变成%20这种%打头的东西,但是new File的时候%20是无法解析成空格,所以在此需要做URLDecoder.decode处理 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
classFilePath = URLDecoder.decode(classFilePath, EncodeConstants.ENCODING_UTF_8); |
|
|
|
|
|
|
|
} catch (UnsupportedEncodingException e1) { |
|
|
|
|
|
|
|
FRContext.getLogger().error(e1.getMessage(), e1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
FRContext.getLogger().info("ClassFilePath:" + classFilePath); |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* alex:如果是jar包中的class文件 |
|
|
|
|
|
|
|
* file:/D:/opt/FineReport6.5/WebReport/WEB-INF/lib/fr-server-6.5.jar!/com/fr/rpt/script/function |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
for (String fileName : findClassNamesUnderFilePath(classFilePath)) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
Class<?> cls = Class.forName(pkgName + "." + fileName.substring(0, fileName.length() - 6)); |
|
|
|
|
|
|
|
if (StableUtils.classInstanceOf(cls, iface)) { |
|
|
|
|
|
|
|
Function inst; |
|
|
|
|
|
|
|
inst = (Function)cls.newInstance(); |
|
|
|
|
|
|
|
for (int fi = 0; fi < EMBFUNCTIONS.length; fi++) { |
|
|
|
|
|
|
|
if (EMBFUNCTIONS[fi].test(inst)) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return all.toArray(new NameAndDescription[0]); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
}; |
|
|
|
} catch (InstantiationException e) { |
|
|
|
|
|
|
|
} catch (IllegalAccessException e) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|