POI之自定義註解生成文件-yellowcong
阿新 • • 發佈:2019-01-09
到處資料如果是成條的,而且是批量處理的情況下,我們可以通過對資料模型使用註解,來解決這種問題。
環境搭建
<!-- excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- word -->
<dependency>
<groupId >org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.17</version>
</dependency>
<!-- xlsx -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version >3.17</version>
</dependency>
<!-- xlsx 依賴這個包 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
實現思路
在資料模型物件下,新增一個註解類,說明抽出的欄位在excel中所對應的表頭,然後通過工具類,通過反射,獲取每個欄位所對應的標題,同時設定每個欄位對應的資料
POI自定義註解類(HFFSAlias.java)
HFFSAlias.java
這個註解類是基於file欄位的,用於說明欄位在xls中的中文名稱
package com.yellowcong.utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 通過註解來操作別名,判斷的是欄位上的別名
* 通過這個註解類和我們的PoiUtils 可以很好的解決文件匯出的問題
*
* @author yellowcong
* @date 2016年1月7日
*/
@Target({ java.lang.annotation.ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface HFFSAlias{
//設定別名,通過value 這個函式,就不需要寫引數了
//@HFFSAlias("xx") 就可以了
public String value() default "";
}
模型類物件(User.java)
這個模型物件,使用的是Hibernate的框架,通過Hibernate做ORM框架,我們只需要呼叫自定義的
HFFSAlias.java
這個annotation,就可以設定標題了
package com.yellowcong.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Index;
import org.hibernate.validator.constraints.Email;
import com.yellowcong.utils.HFFSAlias;
/**
* yellowcong 的新網站,以後也會一直建立下去
*
* @author yellowcong
*
*
* QQ
* 新浪微博藉口
* 微信介面 資料推送
* github 介面
*
*
* 使用者登入的 email
* 使用者登入的手機號
* 使用者的名稱
* 必須是唯一的
*
*
* 網名和頭像隨機生成,如果使用者不填寫的情況下
*
*
* 手機和email登入
* 三方登入
*
*/
@Entity
@Table(name="ycg_user")
public class User {
@HFFSAlias("編號")
private int id;
//使用者名稱稱
@HFFSAlias("使用者名稱稱")
private String username;
//別名
//別名不能存在郵箱@ 不然會導致問題,我們需要做js驗證
@HFFSAlias("別名")
private String nickname;
//密碼
// @HFFSAlias("密碼")
private String password;
//建立日期
@HFFSAlias("建立日期")
private Date createDate;
//使用者頭像
@HFFSAlias("使用者頭像")
private String imgUrl;
//使用者的郵箱地址
@HFFSAlias("郵箱")
private String email;
//電話
@HFFSAlias("電話號")
private String phone;
//是否郵箱啟用
// 0 表示未啟用
// 1 表示啟用
@HFFSAlias("是否啟用")
private int isActive;
public User() {
super();
// TODO Auto-generated constructor stub
}
//用於驗證使用者是否登入
public User(int id) {
super();
this.id = id;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Index(name="username")
@Column(name="username",length=16)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
//我們不需要設定我們的nickname 為唯一的
@Index(name="nickname")
@Column(name="nickname",length=16,unique=false)
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Column(length=32)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(name="create_date")
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
@Column(name="img_url")
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
@Email
@Column(unique=true,length=64)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name="phone",length=11,unique=true)
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Column(name="is_active",length=1)
public int getIsActive() {
return isActive;
}
public void setIsActive(int isActive) {
this.isActive = isActive;
}
}
工具類
工具類中,主要可供使用的是將獲取的
List<?>
資料轉化為Excel文件,同時也可以 將轉化的Excel文件轉化為List<?>
,這幾個方法都有一個特點,他們是有表頭,而且還有一點缺點,對於時間型別需要指定,不然有問題,同時逆向工程中,讀取的表的資料 少一行
package com.yellowcong.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
/**
* 報表工具包,用來操作報表中的資料
* @author yellowcong
* @date 2016年1月6日
* 依賴 poi-2.5.1.jar
* beanutil
*
* 重要的方法
* 建立xls檔案
*
* 基於annotation
* 類物件需要使用@HHFSAlias 註解 給欄位設定別名
* createHSSFByAnnotation(List<?>)
*
* 自己配置好對映關係,然後傳 List<?>物件
* createHSSF(LinkedHashMap<String, String>, List<?>)
*
* 逆向生成 java物件
* 類物件需要使用@HHFSAlias 註解 給欄位設定別名
* reverseHSSFByAnnotation(File, Class<?>)
*
* 對於文件直接使用,使用String[]來配置我們的 對映關係,boolena 設定是否有表頭
* reverseHSSF(File, String[], Class<?>, boolean)
*
* 對於有表頭的可以使用這個,也可以使用上一個,上一個的效率高一點
* reverseHSSF(File, Map<String, String>, Class<?>)
*/
public class PoiUtils {
private PoiUtils(){}
/**
* 將HSSFWorkbook 物件轉化為 檔案
* @param wb HSSFWorkbook 物件
* @param file 檔案
*/
public static void copyHSSFToFile(HSSFWorkbook wb,File file){
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
} catch (Exception e) {
// TODO: handle exception
}finally{
try {
if(wb != null){
wb.write(out);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(out != null){
out.close();
}
} catch (Exception e2) {
// TODO: handle exception
}
}
}
}
/**
* 獲取一個不帶有輸入流的HSSFWorkbook 物件
* @return
*/
public static HSSFWorkbook getHSSFWorkbook(){
return getHSSFWorkbook(null);
}
/**
* 獲取我們的一個HSSFWorkbook
* @return
*/
public static HSSFWorkbook getHSSFWorkbook(File file){
HSSFWorkbook workbook= null;
try {
if(file != null){
workbook = new HSSFWorkbook(new FileInputStream(file));
}else{
workbook = new HSSFWorkbook();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return workbook;
}
//判斷資料
/**
* 通過 結合註解,來生成我們的poi文件操作
* 通過註解呼叫的時候,我們的類中必須要有註解寫出,如果沒有就建立不了
* 需要結合@HFFSAlias 這個類
* @param data 我們的資料集合
* @return
*/
public static HSSFWorkbook createHSSFByAnnotation(List<?> data) {
LinkedHashMap<String, String> title = null;
//獲取到我們的title資料
if(data != null && data.size() >0){
title = new LinkedHashMap<String, String>();
/*Field [] fields = data.get(0).getClass().getDeclaredFields();
for(Field field:fields){
HFFSAlias alias = field.getAnnotation(HFFSAlias.class);
if(alias != null){
title.put(field.getName(), alias.value());
}
}*/
title = PoiUtils.getAnnotationAlias(data.get(0).getClass());
//然後呼叫已經存在的方法來生成表
return PoiUtils.createHSSF(title, data);
}
return null;
}
/**
* 逆向生成,我們需要的資料,將xls中的資料轉化為字串資料
* @param clazz 對映的類物件
* @param file xls檔案物件
* @return
*/
public static List<?> reverseHSSFByAnnotation(File file,Class<?> clazz ){
//獲取field中的資訊
return PoiUtils.reverseHSSF(file, PoiUtils.getAnnotationAlias(clazz), clazz);
}
/**
* 通過clazz 來獲取到裡面的title資料
* @param clazz
* @return
*/
public static LinkedHashMap<String,String> getAnnotationAlias(Class<?> clazz){
Field [] fields = clazz.getDeclaredFields();
LinkedHashMap<String,String> title = new LinkedHashMap<String, String>();
for(Field field:fields){
HFFSAlias alias = field.getAnnotation(HFFSAlias.class);
if(alias != null){
title.put(field.getName(), alias.value());
}
}
return title;
}
/**
* 沒有行標題的資料,需要通過這種方法來確定資料
* 這個方法使用起來 比通過Map 更加的簡便
* @param file 逆向工程的xls檔案愛你
* @param title 這個 需要設定 對映關係,而且順序不能錯
* @param clazz 對映的類物件
* @param hasHead 是否包含表頭,有表頭的資料就 需要從1 開始
* @return
*/
public static List<?> reverseHSSF(File file,String[] title,Class<?> clazz ,boolean hasHead){
try {
//建立work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//獲取第一頁
HSSFSheet sheet = work.getSheetAt(0);
//row是從0開始的,獲取列和行的數目
HSSFRow row = sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
// System.out.println(rownum);
String [] ids = title;
if(rownum >0 && cellnum >0){
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//遍歷別的資料,、第一行資料 是需要的
int start = 0;
if(hasHead){
start = 1;
}
for(int i=start;i<rownum;i++){
row = sheet.getRow(i);
//例項化物件
Object obj = clazz.newInstance();
//設定屬性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//當時數字型別的資料,不可以轉化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}
//日期型別的資料不好注入,需要判斷,和設定資料的型別
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 通過配置的方法來生成逆向工程
* 給定檔案,將物件轉化為類
* 設定中,sheeet 就一頁
* @param file xls檔名稱
* @param title 標題,表頭關係
* @param clazz 類物件
* @return
*/
public static List<?> reverseHSSF(File file,Map<String,String> title,Class<?> clazz ){
try {
//建立work
HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file);
//獲取第一頁
HSSFSheet sheet = work.getSheetAt(0);
//row是從0開始的,獲取列和行的數目
HSSFRow row = sheet.getRow(0);
List list =null;
List<String> dateFields = null;
int rownum = sheet.getLastRowNum();
int cellnum = row.getLastCellNum();
// System.out.println(rownum);
String [] ids = new String[cellnum];
if(rownum >0 && cellnum >0){
title = PoiUtils.reverseMap(title);
list = new ArrayList();
dateFields = PoiUtils.getDataTimeFiled(clazz);
//這個可以 提取為 獲取第一行資料的資訊
//獲取地一行,標題行 獲取標題對應資訊
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result = "";
//當時數字型別的資料,不可以轉化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
// ids[j]= title.containsValue(value);
result = cell.getNumericCellValue()+"";
}else{
result = cell.getStringCellValue();
}
//獲取的資料進行注入操作
//需要反轉map集合
if(title != null && result != null){
if(!title.containsKey(result)){
throw new RuntimeException("內容不匹配");
}
ids[j] = title.get(result);
}
}
//遍歷別的資料
for(int i=1;i<rownum;i++){
row = sheet.getRow(i);
//例項化物件
Object obj = clazz.newInstance();
//設定屬性
for(int j=0;j<cellnum;j++){
HSSFCell cell = row.getCell((short)j);
String result ="";
//當時數字型別的資料,不可以轉化
if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){
result = cell.getNumericCellValue()+"";
}else{
result =cell.getStringCellValue();
}
//日期型別的資料不好注入,需要判斷,和設定資料的型別
if(dateFields.contains(ids[j])){
BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result));
}else{
BeanUtils.setProperty(obj, ids[j], result);
}
}
list.add(obj);
}
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 通過給定類,來獲取到日期型別的欄位
* 解決日期型別 注入不進去的問題
* @param clazz
* @return
*/
public static List<String> getDataTimeFiled(Class<?> clazz){
Field [] fields = clazz.getDeclaredFields();
List<String> fieldNames = new ArrayList<String>();
for(Field field :fields){
if(field.getType().getSimpleName().equals("Date")){
fieldNames.add(field.getName());
}
}
return fieldNames;
}
/**
* 建立我們的HSSFwork 通過配置別名屬性
* @param tiles 文件的title Map<屬性名稱, 別名>
* @param date List<?> 是一個集合的資料
* @return
*/
public static HSSFWorkbook createHSSF(LinkedHashMap<String, String> title,List<?> data) {
try {
//一個漢字的寬度是 500
//英文260
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
//獲取字元的長度
// sheet.setColumnGroupCollapsed(columnNumber, collapsed);
// sheet.setColumnWidth(column, width);
//寫title中的資料
if(title!= null && title.size() >0){
//建立樣式
HSSFCellStyle style = PoiUtils.getHeadStyle(workbook);
int index = 0;
HSSFRow row = sheet.createRow(0);
//獲取寬度
Map<String, Short> columns = PoiUtils.getColumnWidth(title, data);
//獲取資料
for(Map.Entry<String, String> entry:title.entrySet()){
String val = entry.getValue();
HSSFCell cell = row.createCell((short)index);
//設定單元格的型別
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//設定編碼格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//設定樣式
cell.setCellStyle(style);
cell.setCellValue(val);
sheet.setColumnWidth((short)index, columns.get(entry.getKey()));
index++;
}
//建立樣式
style = PoiUtils.getBodyStyle(workbook);
//設定資料
if(data != null && data.size() >0){
//獲取我們的 data中的資料
//內容從第二行開始
int rownum =1;
for(Object obj:data){
row = sheet.createRow(rownum);
//列從第一行開始
int colnum = 0;
for(Map.Entry<String, String> entry:title.entrySet()){
String key = entry.getKey();
//獲取裝的物件資料
String val = BeanUtils.getProperty(obj, key);
if(val == null || "".equals(val.trim())){
val = "-";
}
HSSFCell cell = row.createCell((short)colnum);
//設定單元格的型別
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
//設定編碼格式
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
//設定樣式
cell.setCellStyle(style);
cell.setCellValue(val);
//增加列
colnum ++;
}
rownum ++;
}
}
}
return workbook;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 計算每個資料列的最適合的寬度
* 通過資料來獲取到我們的資料的寬度
* @param title 資料標題
* @param data 資料幾何
* @return
*/
public static Map<String, Short> getColumnWidth(Map<String, String> title,List<?> data){
try {
//儲存我們的每個屬性的寬度
//Map<屬性明,寬度>
Map<String,Short> map = new HashMap<String, Short>();
if(title!= null && title.size() >0){
//計算我們的第一行的寬度
for(Map.Entry<String, String> entry:title.entrySet()){
int max = PoiUtils.countColumnLength(entry.getValue());
//計算下面的所有 同一欄位的長度
for(Object obj:data){
String val = BeanUtils.getProperty(obj, entry.getKey());
int temp = PoiUtils.countColumnLength(val);
max = max >temp?max:temp;
}
//設定欄位的值
map.put(entry.getKey(), (short)max);
}
}
return map;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
*
* 根據字元的數量,來計算列的寬度
* 不同的資料他的資料長度是不一樣的,所以我們需要計算總體的長度
* @param str 字串
* @return int 計算出列寬
*
*/
private static int countColumnLength(String str){
int len =0;
if(str != null && !"".equals(str.trim())){
//一個漢字的寬度是 500
//英文260
char [] chars = str.toCharArray();
//z
int chines = 0;
int other =0;
for(char val:chars){
if(PoiUtils.isChineseChar(val)){
chines ++;
}else{
other++;
}
}
//獲取到了資料然後計算長度
len = chines*500+other*260+1000;
}
return len;
}
/**
* 建立樣式
* @return
*/
public static HSSFCellStyle getHeadStyle(HSSFWorkbook workbook){
HSSFCellStyle style = workbook.createCellStyle();
//居中顯示
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSongRed10(workbook));
return style;
}
/**
* 建立樣式
* @return
*/
public static HSSFCellStyle getBodyStyle(HSSFWorkbook workbook){
HSSFCellStyle style = workbook.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setFont(PoiUtils.getSong10(workbook));
return style;
}
/**
* 建立字型
* @param fontFamly 字型
* @param size 大小 字號 14 13
* @param color HSSFColor.BLACK.index 這個HSSFColor中 的資料
* @param isBold 是否加粗 操作
* @return
*/
//字號 是14號 -->對應的short 280寬度 是 14好字型
public static HSSFFont creatFont(HSSFWorkbook workbook,String fontFamly,Integer size,Short color,boolean isBold){
HSSFFont font = workbook.createFont();
//字號 是14號 -->對應的short 寬度 是 14好字型
font.setFontHeight((short)((size== null || size ==0 )?280:size*20));
font.setBoldweight(isBold?HSSFFont.BOLDWEIGHT_BOLD:HSSFFont.BOLDWEIGHT_NORMAL);
//將字型顏色變紅色
// short colors = HSSFColor.RED.index;
font.setColor(color== null?HSSFColor.BLACK.index:color);
return font;
}
/**
* 判斷我們的資料 中,是否有中文字元
* @param str
* @return
*/
public static boolean isChineseChar(char str){
boolean flag = false;
Pattern pattern = Pattern.compile("[\u4e00-\u9fa5]");
if(pattern.matcher(str+"").find()){
flag = true;
}
return flag;
}
/**
* 得到宋體10 號 黑色
* 預設字型的大小就是10 號
* @return
*/
public static HSSFFont getSong10(HSSFWorkbook workbook){
return creatFont(workbook,"宋體", 10, null,false);
}
/**
* 得到宋體10 號 紅色加粗
* @return
*/
public static HSSFFont getSongRed10(HSSFWorkbook workbook){
return creatFont(workbook,"宋體", 10, HSSFColor.RED.index,true);
}
/**
* 將Map
* @param map
* @return
*/
public static Map<String,String> reverseMap(Map<String,String> map){
Map<String,String> newMap = null;
//將便利的結果重新裝填一下
if(map != null && map.size() >0){
newMap = new LinkedHashMap<String, String>();
for(Map.Entry<String, String> entry:map.entrySet()){
newMap.put(entry.getValue(), entry.getKey());
}
}
return newMap;
}
}
測試類
主要測試了,自己手動編寫表頭和自動配置生成表頭兩種方法,同時還測試了Excel轉List資料
package com.yellowcong.test;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.yellowcong.model.Pager;
import com.yellowcong.model.SystemContext;
import com.yellowcong.model.User;
import com.yellowcong.service.UserService;
import com.yellowcong.utils.HFFSAlias;
import com.yellowcong.utils.PoiUtils;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:ApplicationContext.xml")
public class UserServiceTest {
private UserService userService;
@Resource(name="userService")
public void setUserService(UserService userService) {
this.userService = userService;
}
@Test
public void testList(){
List<User> users= userService.listByPager().getData();
//可以做成annotation的
Map<String,String> map = new HashMap<String, String>();
map.put("id", "編號");
map.put("username", "使用者名稱");
map.put("nickname", "別名");
map.put("password", "密碼");
map.put("createDate", "建立日期");
map.put("imgUrl", "頭像路徑");
map.put("email", "郵箱");
map.put("phone", "電話");
map.put("isActive", "是否啟用");
HSSFWorkbook work = PoiUtils.createHSSF(map, users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}
@Test
public void testList2Anotation(){
List<User> users= userService.listByPager().getData();
HSSFWorkbook work = PoiUtils.createHSSFByAnnotation(users);
PoiUtils.copyHSSFToFile(work, new File("D:/users.xls"));
}
}