titi0006
Messages postés157Date d'inscriptionlundi 24 juin 2013StatutMembreDernière intervention15 mars 2017
-
Modifié par KX le 2/05/2015 à 16:08
KX
Messages postés16734Date d'inscriptionsamedi 31 mai 2008StatutModérateurDernière intervention24 avril 2024
-
2 mai 2015 à 16:16
Bonjour,
J'ai une application développée en Java SWING et je souhaiterai lire un fichier binaire qui va me permettre de récréer mon planning tel qu'il a été enregistrer par mon application.
J'ai donc fait une classe lire fichier mais après je bloque.
Voici mon application:
public class Accueil extends JFrame implements ActionListener {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private JRadioButton newPlanning;
private JRadioButton openPlanning;
private JPanel panAccueil;
private ButtonGroup groupChoixAccueil;;
private JFileChooser chooser;
private Deserialiser deserialise;
private JButton buttonOk;
private ChoixAnnee choixAnnee;
private CalendarWeekViewer calendrier;
private String uneAnnee;
public Accueil() {
setTitle("Gestion d'emploi du temps");
initUI();
panAccueil = new JPanel();
panAccueil.setBorder(new TitledBorder("Que voulez-vous faire ?"));
newPlanning = new JRadioButton("Créer un nouveau planning");
openPlanning = new JRadioButton("Ouvrir un planning existant");
groupChoixAccueil = new ButtonGroup();
groupChoixAccueil.add(newPlanning);
panAccueil.add(newPlanning);
groupChoixAccueil.add(openPlanning);
panAccueil.add(openPlanning);
Container contenu = getContentPane();
contenu.add(panAccueil);
buttonOk = new JButton("Ok");
contenu.add(buttonOk, "South");
buttonOk.addActionListener(this);
}
private void initUI() {
setSize(500, 200);
this.setLocationRelativeTo(null);
}
public static void main(String[] args) {
Accueil frame = new Accueil();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent arg0) {
if (openPlanning.isSelected()) {
chooser = new JFileChooser();
chooser.setVisible(true);
chooser.setCurrentDirectory(new File("/Documents"));
int retrival = chooser.showOpenDialog(null);
if (retrival == JFileChooser.APPROVE_OPTION) {
try {
File fichier = (chooser.getSelectedFile());
deserialise = new Deserialiser(fichier);
calendrier = null;
uneAnnee = deserialise.deserialiser();
calendrier = new CalendarWeekViewer(uneAnnee);
calendrier.setVisible(true);
this.setVisible(false);
} catch (Exception ex) {
ex.printStackTrace();
}
}
} else if (newPlanning.isSelected()) {
choixAnnee = new ChoixAnnee();
choixAnnee.setVisible(true);
this.setVisible(false);
} else {
JOptionPane.showMessageDialog(this, "Aucune action n'a été choisit !", "Erreur", JOptionPane.ERROR_MESSAGE);
}
}
public String getUneAnnee() {
return uneAnnee;
}
public void setUneAnnee(String uneAnnee) {
this.uneAnnee = uneAnnee;
}
}
public class ChoixAnnee extends JFrame implements ActionListener {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private JList liste;
private String[] annee = { "2010-2011", "2011-2012", "2012-2013", "2013-2014", "2014-2015", "2015-2016", "2016-2017", "2017-2018", "2018-2019", "2019-2020" };
private JButton creer;
private JPanel panneau1;
private JLabel label;
private CalendarWeekViewer calendrier;
public ChoixAnnee() {
setTitle("Gestion d'emploi du temps");
initUI();
panneau1 = new JPanel();
liste = new JList(annee);
creer = new JButton("Créer le calendrier");
label = new JLabel("Veuillez choisir une année scolaire pour créer le planning");
label.setFont(new Font(null, Font.BOLD, 16));
liste.setSelectedIndex(5);
liste.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane ascenseur = new JScrollPane(liste);
ascenseur.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
panneau1.setLayout(new BorderLayout());
panneau1.add(BorderLayout.CENTER, ascenseur);
panneau1.add(BorderLayout.SOUTH, creer);
add(label);
add(panneau1);
creer.addActionListener(this);
}
private void initUI() {
setSize(500, 200);
setLocationRelativeTo(null);
}
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(creer)) {
Object valeur = liste.getSelectedValue();
Annee annee = new Annee();
String uneAnnee = annee.convertirUneAnnee(valeur.toString());
calendrier = new CalendarWeekViewer(uneAnnee);
calendrier.setVisible(true);
this.setVisible(false);
}
}
}
public class CalendarWeekViewer extends JFrame implements ActionListener, ItemListener {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
Calendar calendar;
DateFormat dateFormatter;
CalendarWeekTableModel tableModel;
JLabel monthLabel, yearLabel;
JTable table;
int anneeChoisit;
int anneeActuelle;
int anneeDefinitif = 0;
private WeekPanel weekPanel;
private JCheckBox saturdayCheck;
private JCheckBox sundayCheck;
private JMenuBar barreMenu;
private JMenu fichier, edition;
private JMenuItem sauvegarder, fermer, copier, coller;
private Serialiser serialise;
private final static Color COLOR_SELECTION = Color.GRAY;
public CalendarWeekViewer(String uneAnnee) {
setTitle("Gestion d'emploir du temps");
setSize(1400, 800);
this.setLocationRelativeTo(null);
barreMenu = new JMenuBar();
fichier = new JMenu("Fichier");
barreMenu.add(fichier);
sauvegarder = new JMenuItem("Sauvegarder");
fichier.add(sauvegarder);
sauvegarder.addActionListener(this);
fermer = new JMenuItem("Fermer");
fichier.add(fermer);
edition = new JMenu("Edition");
barreMenu.add(edition);
copier = new JMenuItem("Copier");
edition.add(copier);
coller = new JMenuItem("Coller");
edition.add(coller);
setJMenuBar(barreMenu);
Annee annee = new Annee();
anneeChoisit = annee.anneeChoisit(uneAnnee);
calendar = Calendar.getInstance();
anneeActuelle = calendar.get(Calendar.YEAR);
if (anneeActuelle < anneeChoisit) {
anneeDefinitif = anneeChoisit - anneeActuelle;
calendar.add(Calendar.YEAR, +anneeDefinitif);
} else if (anneeActuelle > anneeChoisit) {
anneeDefinitif = anneeActuelle - anneeChoisit;
calendar.add(Calendar.YEAR, -anneeDefinitif);
} else {
calendar.add(Calendar.YEAR, 0);
}
calendar.set(Calendar.MONTH, Calendar.SEPTEMBER); // on se positionne sur le mois de septembre (le début de notre liste de semaines)
calendar.set(Calendar.WEEK_OF_MONTH, 1); // on se positionne sur la première semaine du mois
calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); // on se positionne sur le premier jour officiel
dateFormatter = new SimpleDateFormat("MMMM yyyy");
createComponents();
}
private void createComponents() {
JLabel holidays = new JLabel("Jours Non-ouvrés");
holidays.setFont(new Font(null, Font.BOLD, 12));
JPanel checkBox = new JPanel();
checkBox.add(holidays);
saturdayCheck = new JCheckBox("Samedi");
sundayCheck = new JCheckBox("Dimanche");
checkBox.add(saturdayCheck);
checkBox.add(sundayCheck);
saturdayCheck.addItemListener(this);
sundayCheck.addItemListener(this);
yearLabel = new JLabel("Année : " + anneeChoisit + "-" + (anneeChoisit + 1));
yearLabel.setFont(new Font(null, Font.BOLD, 20));
monthLabel = new JLabel(getWeekLabel());
monthLabel.setFont(new Font(null, Font.PLAIN, 18));
monthLabel.setVerticalAlignment(JLabel.CENTER);
monthLabel.setHorizontalAlignment(JLabel.CENTER);
tableModel = new CalendarWeekTableModel(calendar);
table = new JTable((TableModel) tableModel);
table.setPreferredScrollableViewportSize(new Dimension(1400, 600));
table.setDefaultRenderer(String.public class, new CalendarWeekRenderer());
table.setRowHeight(200);
TableColumnModel tableColumnModel = table.getColumnModel();
for (int i = 0; i < tableColumnModel.getColumnCount(); ++i) {
TableColumn tableColumn = tableColumnModel.getColumn(i);
tableColumn.setPreferredWidth(200);
}
JScrollPane tableScrollPane = new JScrollPane(table);
JPanel southPanel = new JPanel(new BorderLayout()); // le panel intermédiaire
// on créé le weekPanel
weekPanel = new WeekPanel(this, calendar, anneeActuelle, anneeChoisit);
// on met le weekPanel dans le panel intermédiaire
southPanel.add(weekPanel, BorderLayout.SOUTH);
JPanel panneau1 = new JPanel(new BorderLayout());
panneau1.setPreferredSize(new Dimension(1400, 600));
panneau1.add(monthLabel, BorderLayout.NORTH);
panneau1.add(tableScrollPane, BorderLayout.CENTER);
panneau1.add(southPanel, BorderLayout.SOUTH);
JPanel panneau3 = new JPanel(new BorderLayout());
JPanel panneau2 = new JPanel(new BorderLayout());
panneau2.add(yearLabel, BorderLayout.WEST);
panneau2.add(checkBox, BorderLayout.EAST);
panneau3.add(panneau2, BorderLayout.NORTH);
panneau3.add(panneau1);
this.add(panneau3);
}
public void setWeek(Calendar selection) {
calendar.setTime(selection.getTime());
monthLabel.setText(getWeekLabel());
tableModel.fireTableStructureChanged();
weekPanel.updateSelection();
}
private String getWeekLabel() {
Calendar calendar = Calendar.getInstance(); // on créé un nouveau calendar pour pas modifier le calendar qui nous sert pour savoir quelle semaine on affiche
calendar.setTime(this.calendar.getTime()); // on recopie dedans la date actuellement mise dans le calendar qui ser pour savoir quelle semaine on affiche
int week = calendar.get(Calendar.WEEK_OF_YEAR); // on récupère le numéro de la semaine
// le premier jour de cette semaine
Date premierJour = calendar.getTime();
int monthPremierJour = calendar.get(Calendar.MONTH); // bug ici (il y avait int monthPremierJour = calendar.get(Calendar.MONDAY); donc n'importe quoi)!!!
// le dernier jour de cette semaine
calendar.add(Calendar.WEEK_OF_YEAR, 1);
calendar.add(Calendar.DAY_OF_WEEK, -1);
Date dernierJour = calendar.getTime();
int monthDernierJour = calendar.get(Calendar.MONTH);
return WeekPanel.getWeekLabel(week, premierJour, dernierJour, monthPremierJour, monthDernierJour);
}
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source.equals(sauvegarder)) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new File("/Documents"));
int retrival = chooser.showSaveDialog(null);
if (retrival == JFileChooser.APPROVE_OPTION) {
try {
String fw = (chooser.getSelectedFile() + "_" + anneeChoisit + "_" + (anneeChoisit + 1) + ".dat");
serialise = new Serialiser(fw);
serialise.serialiser(this);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
@Override
public void itemStateChanged(ItemEvent event) {
Object source = event.getSource();
if (source.equals(saturdayCheck)) {
tableModel.getColumnName(6);
tableModel.isCellEditable(1, 5);
setBackground(COLOR_SELECTION);
}
}
}
public class CalendarWeekRenderer extends JLabel implements TableCellRenderer {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
public CalendarWeekRenderer() {
super();
setFont(new Font(null, Font.PLAIN, 28));
setVerticalAlignment(JLabel.TOP);
setHorizontalAlignment(JLabel.LEFT);
setOpaque(true);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
setText((String) value);
return this;
}
}
public class CalendarWeekTableModel extends AbstractTableModel {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private Calendar calendar;
private List<Journee> modeleJournee = new ArrayList<Journee>();
public CalendarWeekTableModel(Calendar calendar) {
this.calendar = calendar;
}
public int getColumnCount() {
// ramene les 7 jours de la semaine
return 7;
}
public int getRowCount() {
// ramene le nombre de ligne
// nombre de demi journée
return 2;
}
public String getColumnName(int col) {
return getDayName(col);
}
public Object getValueAt(int row, int col) {
return "";
}
public boolean isCellEditable(int row, int col) {
return true;
}
public void setValueAt(Object value, int row, int col) {
Journee journee = modeleJournee.get(col);
if (col == 0) {
journee.getMatin().setLibelle(value.toString());
}
if (col == 1) {
journee.getApresmidi().setLibelle(value.toString());
}
}
private String getDayName(int day) {
Calendar calendarDay = Calendar.getInstance();
calendarDay.setTime(calendar.getTime());
calendarDay.add(Calendar.DATE, day);
return calendarDay.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()) + " " + calendarDay.get(Calendar.DATE); // bug sur cette ligne calendar au lieu de calendarDay
}
protected void intialiserJournee() {
// initalisation de la journée
for (int i = 0; i < 7; i++) {
modeleJournee.add(new Journee());
}
}
}
public class WeekPanel extends JPanel {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static final int CALENDAR_START = Calendar.SEPTEMBER;
private final static Color COLOR_ODD = Color.WHITE;
private final static Color COLOR_SELECTION = Color.YELLOW;
private final Calendar calendar;
private JLabel[] labels;
private Calendar selection; // la sélection est gérée par un Calendar et non plus un int
private CalendarWeekViewer viewer;
private JPanel weekPanel;
private int anneeActuelle;
private int anneeChoisit;
private int anneeDefinitif = 0;
private Map<Integer, Integer> weeks = new HashMap<>(); // on a besoin de retrouver la position d'une semaine dans la liste de label
public WeekPanel(CalendarWeekViewer viewer, Calendar calendar, int anneeActuelle, int anneeChoisit) {
this.viewer = viewer;
this.calendar = calendar;
this.anneeActuelle = anneeActuelle;
this.anneeChoisit = anneeChoisit;
// comme on ne va plus de la semaine 0 à la dernière, mais d'une semaine en plein milieu de l'année à une autre de l'année suivantre, on ne peut plus utiliser un entier pour la sélection : il est plus simple d'utiliser un calendar (parce qu'on a besoin d'une numéro de la seamine, mais aussi de l'année)
selection = copyCalendar(calendar);
setLayout(new BorderLayout());
Calendar premiereSemaine = getPremiereSemaine(CALENDAR_START, anneeActuelle, anneeChoisit);
Calendar derniereSemaine = getDerniereSemaine(CALENDAR_START);
int nbSemaines = compteSemaines(premiereSemaine, derniereSemaine);
labels = new JLabel[nbSemaines];
weekPanel = new JPanel(new GridLayout(1, 0));
Calendar semaine = Calendar.getInstance(); // on a besoin de parcourir les semaines...
semaine.setTime(premiereSemaine.getTime()); // on les parcourt de la première à la dernière
for (int i = 0; i < nbSemaines; i++) {
final Calendar week = copyCalendar(semaine);
weeks.put(week.get(Calendar.WEEK_OF_YEAR), i); // ceci nous permet de facilement trouver le numéro d'une semaine dans la liste de label
JLabel label = new JLabel(getWeekLabel(semaine), JLabel.CENTER);
label.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
label.setOpaque(true);
labels[i] = label;
setLabelColor(week);
label.setEnabled(true);
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
setSelection(week);
}
});
weekPanel.add(label);
semaine.add(Calendar.WEEK_OF_YEAR, 1); // on avance d'une semaine
}
setLabelColor(selection);
JScrollPane slider = new JScrollPane(weekPanel);
slider.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
add(slider, BorderLayout.CENTER);
}
private Calendar copyCalendar(Calendar calendar) {
Calendar copy = Calendar.getInstance();
copy.setTime(calendar.getTime());
return copy;
}
private int compteSemaines(Calendar premiereSemaine, Calendar derniereSemaine) {
Calendar calendar = copyCalendar(premiereSemaine); // on prend une nouvelle instance
int count = 0;
while (calendar.get(Calendar.WEEK_OF_YEAR) != derniereSemaine.get(Calendar.WEEK_OF_YEAR)) { // tant qu'on a pas atteint la dernière semaine
count++; // on compte une semaine de plus
calendar.add(Calendar.WEEK_OF_YEAR, 1); // on avance d'une semaine
}
return count + 1; // on ajoute 1 parce qu'il faut compte la dernière semaine (qu'on a pas compter dans la boucle)
}
public Calendar getPremiereSemaine(int month, int uneAnneeActuelle, int uneAnneeChoisit) {
Calendar calendar = Calendar.getInstance();
if (uneAnneeActuelle < uneAnneeChoisit) {
anneeDefinitif = uneAnneeChoisit - uneAnneeActuelle;
calendar.add(Calendar.YEAR, +anneeDefinitif);
} else if (uneAnneeActuelle > uneAnneeChoisit) {
anneeDefinitif = uneAnneeActuelle - uneAnneeChoisit;
calendar.add(Calendar.YEAR, -anneeDefinitif);
} else {
calendar.add(Calendar.YEAR, 0);
}
calendar.set(Calendar.MONTH, month); // on se positionne sur le mois demandé
calendar.set(Calendar.WEEK_OF_MONTH, 1); // on se positionne sur la première semaine du mois
calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); // on se positionne sur le premier jour officiel
return calendar;
}
public Calendar getDerniereSemaine(int month) {
Calendar calendar = getPremiereSemaine(month, anneeActuelle, anneeChoisit); // on se place sur la première semaine du mois
calendar.add(Calendar.YEAR, 1); // on avance d'un an
calendar.add(Calendar.WEEK_OF_YEAR, -1); // on recule d'une semaine
return calendar;
}
private void setLabelColor(Calendar calendar) {
JLabel label = labels[weeks.get(calendar.get(Calendar.WEEK_OF_YEAR))]; // on utilise la map pour savoir le numéro du label correspondant à la semaine
if (calendar.equals(selection)) {
label.setBackground(COLOR_SELECTION); // la semaine sélectionnée
} else {
label.setBackground(COLOR_ODD);
}
}
public void updateSelection() {
Calendar oldSelection = selection;
selection = copyCalendar(calendar);
setLabelColor(oldSelection);
setLabelColor(selection);
weekPanel.scrollRectToVisible(labels[weeks.get(selection.get(Calendar.WEEK_OF_YEAR))].getBounds());
}
private void setSelection(Calendar selection) {
Calendar oldSelection = this.selection;
this.selection = selection;
setLabelColor(oldSelection);
setLabelColor(selection);
viewer.setWeek(selection);
}
private String getWeekLabel(Calendar week) {
Calendar calendar = copyCalendar(week);
// le premier jour de cette semaine
Date premierJour = calendar.getTime();
int monthPremierJour = calendar.get(Calendar.MONTH);
// le dernier jour de cette semaine
calendar.add(Calendar.WEEK_OF_YEAR, 1);
calendar.add(Calendar.DAY_OF_WEEK, -1);
Date dernierJour = calendar.getTime();
int monthDernierJour = calendar.get(Calendar.MONTH);
return getWeekLabel(week.get(Calendar.WEEK_OF_YEAR), premierJour, dernierJour, monthPremierJour, monthDernierJour);
}
private final static DateFormat DATE_DEBUT_FORMAT = new SimpleDateFormat("dd");
private final static DateFormat DATE_FORMAT = new SimpleDateFormat("dd MMMM");
public static String getWeekLabel(int week, Date premierJour, Date dernierJour, int monthPremierJour, int monthDernierJour) {
String debut;
if (monthDernierJour == monthPremierJour) {
debut = DATE_DEBUT_FORMAT.format(premierJour);
} else {
debut = DATE_FORMAT.format(premierJour);
}
String milieu = DATE_FORMAT.format(dernierJour);
String fin = " (" + week + ")";
return debut + " - " + milieu + fin;
}
}
public class Serialiser implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static String fichier;
public Serialiser(String fichier) {
Serialiser.fichier = fichier;
}
public void serialiser(CalendarWeekViewer calendrier) {
try {
FileOutputStream fout = new FileOutputStream(fichier);
ObjectOutputStream oout = new ObjectOutputStream(fout);
oout.writeObject(calendrier);
System.out.println("Le planning a été sérialisé !");
oout.close();
fout.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
public class Deserialiser implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private static File fichier;
public Deserialiser(File fichier) {
Deserialiser.fichier = fichier;
}
public String deserialiser() {
String uneAnnee = null;
try {
FileInputStream fis = new FileInputStream(fichier);
ObjectInputStream ois = new ObjectInputStream(fis);
CalendarWeekViewer calendar = (CalendarWeekViewer) ois.readObject();
ois.close();
fis.close();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (public classNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return uneAnnee;
}
}
public class Annee {
private String annee;
public Annee(String annee) {
this.setAnnee(annee);
}
public Annee() {
// TODO Auto-generated constructor stub
}
public String getAnnee() {
return annee;
}
public void setAnnee(String annee) {
this.annee = annee;
}
public String convertirUneAnnee(String anneeListe) {
String uneAnnee;
uneAnnee = anneeListe.substring(0, 4);
return uneAnnee;
}
public int anneeChoisit(String uneAnnee) {
SimpleDateFormat curFormater = new SimpleDateFormat("yyyy");
Date dateObj = null;
int year = 0;
try {
dateObj = curFormater.parse(uneAnnee);
Calendar calendar2 = Calendar.getInstance();
calendar2.setTime(dateObj);
year = calendar2.get(Calendar.YEAR);
} catch (ParseException e) {
e.printStackTrace();
}
return year;
}
}
public class Journee {
private ModuleJour matin;
private ModuleJour apresmidi;
public Journee() {
super();
matin = new ModuleJour();
apresmidi = new ModuleJour();
}
public ModuleJour getMatin() {
return matin;
}
public void setMatin(ModuleJour matin) {
this.matin = matin;
}
public ModuleJour getApresmidi() {
return apresmidi;
}
public void setApresmidi(ModuleJour apresmidi) {
this.apresmidi = apresmidi;
}
}
public class ModuleJour {
private String libelle = "";
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
}
KX
Messages postés16734Date d'inscriptionsamedi 31 mai 2008StatutModérateurDernière intervention24 avril 20243 015 2 mai 2015 à 16:16
Bonjour,
Il y a un bug sur le forum :
Si tu utilises les balises < code></code> cela va interpréter les * comme des puces et casser toute l'indentation du code.
J'ai rattrapé ton message comme j'ai pu en mettant des balises < code java></code> à la place, mais c'est une indentation par défaut, pas celle que tu avais pu mettre à l'origine.
À l'avenir, merci d'utiliser les balises de code spécifique à Java (c'est plus joli avec la coloration syntaxique), ainsi que le bouton "prévisualiser" avant d'envoyer le message, pour être sûr qu'il soit bien formaté.
NB. Le bug a été remonté aux développeurs du site.