View Javadoc

1   /**
2    * Copyright 2008 WebPhotos
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.webphotos;
17  
18  import java.io.File;
19  import java.sql.SQLException;
20  import java.sql.Statement;
21  import java.text.SimpleDateFormat;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Set;
26  import javax.swing.JOptionPane;
27  import net.sf.webphotos.dao.jpa.AlbumDAO;
28  import net.sf.webphotos.entity.IsPhoto;
29  import net.sf.webphotos.model.AlbumVO;
30  import net.sf.webphotos.model.PhotoVO;
31  import net.sf.webphotos.util.ApplicationContextResource;
32  import net.sf.webphotos.util.Util;
33  import net.sf.webphotos.util.legacy.CacheFTP;
34  import org.apache.log4j.Logger;
35  
36  /**
37   * A classe Album mantém uma coleçao de fotos em um ArrayList de PhotoDTO, que
38   * pode ser manipulada através das funções da própria classe. Classe do tipo
39   * Singleton, é permitido apenas uma instância da classe. O objeto é acessível
40   * unicamente através da classe. Também manipula dados dos IDs, nome do albúm,
41   * descrição, data de inserção e categoria.
42   */
43  public class Album {
44  
45      private static final Album instanciaAlbum = new Album();
46      /**
47       * variáveis do álbum
48       */
49      private int albumID = 0;
50      private int categoriaID = 0;
51      private int usuarioID = 0;
52      private String nmAlbum = null;
53      private String descricao = null;
54      private String dtInsercao = null;
55      /**
56       * variáveis utilizadas nessa classe
57       */
58      private Set<PhotoVO> fotos = new HashSet<PhotoVO>();
59      /**
60       *
61       */
62      private Set<PhotoDTO> fotosNovas = new HashSet<PhotoDTO>();
63      private String[][] categorias = null;
64      private Logger log = Logger.getLogger(this.getClass().getName());
65      private static AlbumDAO albunsDAO = (AlbumDAO) ApplicationContextResource.getBean("albunsDAO");
66  
67      /**
68       * nunca usado publicamente (construtor private)
69       */
70      private Album() {
71      }
72  
73      /**
74       * Retorna o objeto Album instanciado na própria classe.
75       *
76       * @return Retorna um objeto Album.
77       */
78      public static Album getAlbum() {
79          return instanciaAlbum;
80      }
81  
82      /**
83       * Seta um valor para o ID do albúm.
84       *
85       * @param aID ID do álbum.
86       */
87      public void setAlbumID(int aID) {
88          albumID = aID;
89      }
90  
91      /**
92       * Seta um valor para o ID do usuário.
93       *
94       * @param uID ID do usuário.
95       */
96      public void setUsuarioID(int uID) {
97          usuarioID = uID;
98      }
99  
100     /**
101      * Seta um valor para o ID de categoria.
102      *
103      * @param cID ID da categoria.
104      */
105     public void setCategoriaID(int cID) {
106         categoriaID = cID;
107     }
108 
109     /**
110      * Seta um valor para o nome do albúm.
111      *
112      * @param nm Nome do albúm.
113      */
114     public void setNmAlbum(String nm) {
115         nmAlbum = nm;
116     }
117 
118     /**
119      * Seta um valor para a descrição do albúm.
120      *
121      * @param d Descrição do albúm.
122      */
123     public void setDescricao(String d) {
124         descricao = d;
125     }
126 
127     /**
128      * Seta um valor para a data de inserção do albúm.
129      *
130      * @param dt Data de inserção do albúm.
131      */
132     public void setDtInsercao(String dt) {
133         dtInsercao = dt;
134     }
135 
136     /**
137      * Retorna o ID do albúm.
138      *
139      * @return Retorna um ID.
140      */
141     public int getAlbumID() {
142         return albumID;
143     }
144 
145     /**
146      * Retorna o ID do usuário.
147      *
148      * @return Retorna um ID.
149      */
150     public int getUsuarioID() {
151         return usuarioID;
152     }
153 
154     /**
155      * Retorna o ID da categoria.
156      *
157      * @return Retorna um ID.
158      */
159     public int getCategoriaID() {
160         return categoriaID;
161     }
162 
163     /**
164      * Retorna o nome do albúm.
165      *
166      * @return Retorna um nome.
167      */
168     public String getNmAlbum() {
169         return nmAlbum;
170     }
171 
172     /**
173      * Retorna a descrição do albúm.
174      *
175      * @return Retorna uma descrição.
176      */
177     public String getDescricao() {
178         return descricao;
179     }
180 
181     /**
182      * Retorna a data de inserção do albúm.
183      *
184      * @return Retorna uma data.
185      */
186     public String getDtInsercao() {
187         return dtInsercao;
188     }
189 
190     /**
191      * Retorna uma foto deste album ou null se não existir. Faz a busca da foto
192      * através de um ID enviado como parâmetro.
193      *
194      * @param fotoID ID da foto.
195      * @return Retorna uma foto.
196      */
197     public PhotoDTO getFoto(int fotoID) {
198         Iterator<PhotoVO> iter = fotos.iterator();
199 
200         while (iter.hasNext()) {
201             PhotoVO f = iter.next();
202             if (f.getFotoid() == fotoID) {
203                 return new PhotoDTO(f);
204             }
205         }
206         return null;
207     }
208 
209     /**
210      * Retorna uma foto deste album ou null se não existir. Faz a busca da foto
211      * através do caminho do arquivo que foi enviado como parâmetro.
212      *
213      * @param caminho Caminho do arquivo foto.
214      * @return Retorna uma foto.
215      */
216     public PhotoDTO getFoto(String caminho) {
217         Iterator<PhotoDTO> iter = fotosNovas.iterator();
218 
219         while (iter.hasNext()) {
220             PhotoDTO f = iter.next();
221             if (caminho.equals(f.getCaminhoArquivo())) {
222                 return f;
223             }
224         }
225         return null;
226     }
227 
228     /**
229      * Retorna toda a coleção encontrada no ArrayList fotos.
230      *
231      * @return Retorna a coleção de fotos.
232      */
233     public PhotoDTO[] getFotos() {
234         final Set<IsPhoto> allPhotos = getAllPhotos();
235         return allPhotos.toArray(new PhotoDTO[allPhotos.size()]);
236     }
237 
238     /**
239      * Retorna uma matriz com as fotos e seus dados específicos. Armazena para
240      * cada foto o seu ID ou caminho, legenda e crédito.
241      *
242      * @return Retorna todas as fotos e seus valores específicos.
243      */
244     public Object[][] getFotosArray() {
245         Set<IsPhoto> joined = getAllPhotos();
246 
247         Object[][] resultado = new Object[joined.size()][3];
248         Iterator<IsPhoto> iter = joined.iterator();
249         int ct = 0;
250 
251         while (iter.hasNext()) {
252             IsPhoto photo = iter.next();
253             resultado[ct][0] = photo.getKey();
254             resultado[ct][1] = photo.getLegenda();
255             resultado[ct][2] = photo.getCreditos().getNome();
256             ct++;
257         }
258         return resultado;
259     }
260 
261     /**
262      * Retorna um vetor que armazenará os dados de ID, legenda e crédito da
263      * foto.
264      *
265      * @return Retorna dados de ID, legenda e crédito da foto.
266      */
267     public String[] getFotosColunas() {
268         return new String[]{"ID", "Legenda", "Credito"};
269     }
270 
271     /**
272      *
273      * @return
274      */
275     public String[] getCategoriasArray() {
276         return getCategoriasArray(Boolean.FALSE);
277     }
278 
279     /**
280      * Retorna um vetor que armazena as categorias. Checa se categoria é
281      * diferente de null. Caso afirmativo, armazena seus valores no vetor criado
282      * para retorno.
283      *
284      * @return Retorna as categorias do albúm.
285      */
286     public String[] getCategoriasArray(Boolean force) {
287         int tamanho;
288         if (categorias == null || force == true) {
289             try {
290                 log.info("Populando Categorias");
291                 populaCategorias();
292                 tamanho = categorias.length;
293             } catch (SQLException ex) {
294                 log.error("Impossível popular categorias", ex);
295                 tamanho = 0;
296             }
297         } else {
298             tamanho = categorias.length;
299         }
300 
301         String[] nomesCategorias = new String[tamanho];
302         for (int i = 0; i < tamanho; i++) {
303             nomesCategorias[i] = categorias[i][1];
304         }
305 
306         return nomesCategorias;
307     }
308 
309     /**
310      * Retorna um índice da matriz categorias. Faz a busca através de um nome
311      * enviado como parâmetro. Caso não seja encontrado pelo nome, retorna o
312      * valor 0.
313      *
314      * @param nomeCategoria Nome da categoria pesquisada.
315      * @return Retorna um índice de posição.
316      */
317     public int getLstCategoriasIndex(String nomeCategoria) {
318         for (int i = 0; i < categorias.length; i++) {
319             if (nomeCategoria.equals(categorias[i][1])) {
320                 return i;
321             }
322         }
323         return 0;
324     }
325 
326     /**
327      * Retorna um índice da matriz categorias. Faz a busca através de um ID
328      * enviado como parâmetro. Caso não seja encontrado pelo ID, retorna o valor
329      * 0.
330      *
331      * @param categoriaID ID da categoria pesquisada.
332      * @return Retorna um índice de posição.
333      */
334     public int getLstCategoriasIndex(int categoriaID) {
335         for (int i = 0; i < categorias.length; i++) {
336             if (categoriaID == Integer.parseInt(categorias[i][0])) {
337                 return i;
338             }
339         }
340         return 0;
341     }
342 
343     /**
344      * Retorna o ID dado um nome de categoria.
345      *
346      * @param nomeCategoria Nome de categoria pesquisada.
347      * @return Retorna um ID.
348      */
349     public int getLstCategoriasID(String nomeCategoria) {
350         for (int i = 0; i < categorias.length; i++) {
351             if (nomeCategoria.equals(categorias[i][1])) {
352                 return Integer.parseInt(categorias[i][0]);
353             }
354         }
355         return -1;
356     }
357 
358     /**
359      * Limpa o ArrayList fotos. Seta o valor 0 para as variáveis numéricas e
360      * vazio para variáveis de tipo String.
361      */
362     public void clear() {
363         albumID = 0;
364         usuarioID = 0;
365         categoriaID = 0;
366         nmAlbum = "";
367         descricao = "";
368         dtInsercao = "";
369         if (fotos != null) {
370             fotos.clear();
371         }
372     }
373 
374     /**
375      * Carrega um albúm no ArrayList fotos que anteriormente foi salvo no banco
376      * de dados. Após limpar os valores em fotos, faz uma busca ao banco de
377      * dados para carregar informações do albúm especificado. A comparação no
378      * banco de dados é feita através do ID do albúm, passado como parâmetro.
379      *
380      * @param aID ID do albúm.
381      */
382     public void loadAlbum(int aID) {
383 
384         fotos.clear();
385 
386         AlbumVO album = ((AlbumDAO) ApplicationContextResource.getBean("albunsDAO")).findBy(aID);
387 
388         if (album != null) {
389             albumID = aID;
390             categoriaID = album.getCategoriasVO().getCategoriaID();
391             usuarioID = 0;
392             nmAlbum = album.getNmalbum();
393             descricao = album.getDescricao();
394             dtInsercao = new SimpleDateFormat("dd/MM/yyyy").format(album.getDtInsercao());
395         }
396 
397         try {
398 
399             /**
400              * Popula a collection fotos
401              */
402             fotos = album.getPhotos();
403 
404         } catch (Exception e) {
405             log.error("Ocorreu um erro durante a leitura do álbum no banco de dados", e);
406             int selecao = JOptionPane.showConfirmDialog(null,
407                     "ERRO durante leitura do álbum no banco de dados.\n\nTentar Novamente?",
408                     "Aviso!",
409                     JOptionPane.YES_NO_OPTION,
410                     JOptionPane.WARNING_MESSAGE);
411             if (selecao == JOptionPane.YES_OPTION) {
412                 /**
413                  * TODO: extrair para um método
414                  */
415                 loadAlbum(aID);
416 
417             } else {
418                 log.error("Ocorreu um erro inexperado durante a leitura do álbum", e);
419                 JOptionPane.showMessageDialog(null, "ERRO inexperado durante leitura do álbum - " + e.getMessage(), "Erro!", JOptionPane.ERROR_MESSAGE);
420                 throw new RuntimeException(e);
421             }
422         }
423     }
424 
425     /**
426      * Recebe um vetor com IDs de albúns a serem excluídos. Adiciona os albúns
427      * no arquivo FTP, exclui os albúns do banco de dados e por último exclui os
428      * arquivos da pasta local ou rede.
429      *
430      * @param albunsID IDs dos albúns.
431      */
432     public void excluirAlbuns(int[] albunsID) {
433         Statement st = null;
434         boolean sucesso = true;
435 
436         // passo 1 - adiciona o álbum no arquivoFTP
437         for (int i = 0; i < albunsID.length; i++) {
438             CacheFTP.getCache().addCommand(CacheFTP.DELETE, albunsID[i], 0);
439         }
440 
441         // passo 2 - remover do banco de dados
442         try {
443             for (int i = 0; i < albunsID.length; i++) {
444                 albunsDAO.remove(albunsDAO.findBy(i));
445             }
446             log.info("Exclusão dos álbuns e fotos no banco de dados efetuada com sucesso !");
447         } catch (Exception e) {
448             log.error("Houve um erro na exclusao de albuns no banco de dados", e);
449             sucesso = false;
450         } finally {
451             try {
452                 if (st != null) {
453                     st.close();
454                 }
455             } catch (Exception e) {
456                 log.debug("Can't Close Statement", e);
457             }
458         }
459 
460 
461         // passo 3 - remover os arquivos local ou rede
462         for (int i = 0; i < albunsID.length; i++) {
463             File diretorio = new File(BancoImagem.getLocalPath(albunsID[i]));
464             if (diretorio.isDirectory() == true) {
465                 // provavelmente tem fotos nesse diretorio
466                 String[] arquivos = diretorio.list();
467                 if (arquivos.length > 0) {
468                     // apagamos os arquivos desse diretorio
469                     for (int j = 0; j < arquivos.length; j++) {
470                         File arquivo = new File(BancoImagem.getLocalPath(albunsID[i]) + File.separator + arquivos[j]);
471                         if (arquivo.delete() == true) {
472                             log.info(BancoImagem.getLocalPath(albunsID[i]) + File.separator + arquivos[j] + " excluído com sucesso");
473                         } else {
474                             log.error("Erro na exclusão de " + BancoImagem.getLocalPath(albunsID[i]) + File.separator + arquivos[j]);
475                             sucesso = false;
476                         }
477                     }
478                 }
479                 if (diretorio.delete() == true) {
480                     log.info(BancoImagem.getLocalPath(albunsID[i]) + " excluído com sucesso");
481                 } else {
482                     log.error("Erro na exclusão de " + BancoImagem.getLocalPath(albunsID[i]));
483                     sucesso = false;
484                 }
485             }
486         } // fim for
487         if (sucesso) {
488             log.info("Álbum(ns) excluído(s) com sucesso.");
489         } else {
490             log.warn("Finalizando com erros na exclusão.");
491         }
492     }
493 
494     /**
495      * Recebe uma lista com nomes de fotos e faz uma busca no ArrayList fotos,
496      * caso encontre, exclui a foto específica. Essa função exclui fotos da
497      * coleção que ainda não estejam cadastradas no DB nem feito thumbnails
498      * (usuário adicionou e quer excluir essas fotos).
499      *
500      * @param nomes Lista de nomes de fotos.
501      */
502     public void excluirFotos(String[] nomes) {
503         String nome;
504         PhotoVO foto;
505 
506         for (int i = 0; i < nomes.length; i++) {
507             Iterator<PhotoVO> iter = fotos.iterator();
508             nome = nomes[i];
509 
510             while (iter.hasNext()) {
511                 foto = iter.next();
512                 if (nome.equals(foto.getCaminhoArquivo())) {
513                     iter.remove();
514                     break;
515                 }
516             } // fim while
517         } // fim for
518     } // fim metodo
519 
520     /**
521      * Recebe uma lista com IDs das fotos e faz uma busca para excluir as fotos
522      * específicas do DB, FTP e FS.
523      *
524      * @param fotosID Lista de IDs de fotos.
525      */
526     public void excluirFotos(int[] fotosID) {
527         Statement st = null;
528         boolean sucesso = true;
529         int aID = getAlbum().albumID;
530         AlbumVO albumVO = albunsDAO.findBy(aID);
531         String sql;
532 
533         // passo 1 - adicionar o arquivo em ArquivoFTP
534         for (int i = 0; i < fotosID.length; i++) {
535             CacheFTP.getCache().addCommand(CacheFTP.DELETE, aID, fotosID[i]);
536         }
537 
538         // passo 2 - remover do banco de dados
539         try {
540             for (int i = 0; i < fotosID.length; i++) {
541                 albumVO.getPhotos().remove(albumVO.getPhotoBy(fotosID[i]));
542             }
543             albunsDAO.getEntityManager().merge(albumVO);
544         } catch (Exception e) {
545             log.error("Erro na exclusão no banco de dados ", e);
546             sucesso = false;
547         } finally {
548             try {
549                 if (st != null) {
550                     st.close();
551                 }
552             } catch (Exception e) {
553                 log.warn("Can't close statement", e);
554             }
555         }
556 
557         // passo 3 - remover arquivos do fs (original e _a, _b, _c e _d)
558         String[] prefixos = {"", "_a", "_b", "_c", "_d"};
559         String nomeArquivo;
560         boolean encontrou;
561 
562         for (int i = 0; i < fotosID.length; i++) {
563             // pesquisa a foto na coleção deste álbum e remove da colecao
564             encontrou = false;
565             Iterator<PhotoVO> iter = fotos.iterator();
566             while (iter.hasNext() && !encontrou) {
567                 PhotoVO f = iter.next();
568                 if (f.getFotoid() == fotosID[i]) {
569                     encontrou = true;
570                     iter.remove();
571                 }
572             }
573 
574             for (int j = 0; j < prefixos.length; j++) {
575                 nomeArquivo = BancoImagem.getLocalPath(aID) + File.separator + prefixos[j] + fotosID[i] + ".jpg";
576                 File arqFoto = new File(nomeArquivo);
577 
578                 if (arqFoto.isFile()) {
579                     if (arqFoto.delete()) {
580                         log.info(nomeArquivo + " excluído com sucesso");
581                     } else {
582                         log.error(nomeArquivo + " não pode ser excluído ");
583                         sucesso = false;
584                     }
585                 } else {
586                     log.warn(nomeArquivo + " não é arquivo ou não existe");
587                     sucesso = false;
588                 }
589             }
590         }
591 
592         //passo 4 - removendo do site remoto
593         if (Util.getConfig().getBoolean("autoTransferir")) {
594             Thread t = new Thread(new net.sf.webphotos.gui.util.FtpClient());
595             t.start();
596         }
597 
598 
599         if (sucesso) {
600             log.info("Exclusão de fotos finalizada com sucesso.");
601         } else {
602             log.warn("Exclusão de fotos finalizada com erros.");
603         }
604     }
605 
606     /**
607      * Inclui fotos na coleção. Recebe uma lista de arquivos de fotos, que serão
608      * implantados no ArrayList fotos.
609      *
610      * @param photo Lista de arquivos.
611      */
612     public void adicionarFotos(File[] f) {
613         if (f.length == 0) {
614             return;
615         }
616 
617         for (int i = 0; i < f.length; i++) {
618             File novaFoto = f[i];
619             fotosNovas.add(new PhotoDTO(novaFoto.getAbsolutePath()));
620         }
621     }
622 
623     // popula a matriz de creditos, normalmente feito somente na primeira vez
624     // ou na ocasião de um novo crédito adicionado ao banco de dados
625     private void populaCategorias() throws SQLException {
626         String sql = Util.getConfig().getString("sql2");
627 
628         List<Object[]> tableData = albunsDAO.findByNativeQuery(sql);
629 
630         categorias = new String[tableData.size()][2];
631 
632         int ct = 0;
633         for (Object[] objects : tableData) {
634             categorias[ct][0] = objects[0].toString();
635             categorias[ct][1] = objects[1].toString();
636             ct++;
637         }
638     }
639 
640     /**
641      * Retorna a categoria específica. Faz a busca na matriz categorias,
642      * comparando com o ID recebido como parãmetro.
643      *
644      * @param categoriaID ID da categoria.
645      * @return Retorna uma categoria.
646      */
647     public String getCategoria(int categoriaID) {
648         int tamanho;
649         if (categorias == null) {
650             try {
651                 populaCategorias();
652                 tamanho = categorias.length;
653             } catch (SQLException ex) {
654                 tamanho = 0;
655             }
656         } else {
657             tamanho = categorias.length;
658         }
659 
660         for (int i = 0; i < tamanho; i++) {
661             if (categorias[i][0].equals(Integer.toString(categoriaID))) {
662                 return categorias[i][1];
663             }
664         }
665         return "categoria nao encontrada";
666     }
667 
668     /**
669      * Retorna uma String contendo todos os dados do albúm.
670      *
671      * @return Retorna dados do albúm.
672      */
673     @Override
674     public String toString() {
675         Iterator<PhotoVO> iter = fotos.iterator();
676 
677         String msg = "--------------------------------------"
678                 + "\nalbumID    : " + albumID
679                 + "\nusuarioID  : " + usuarioID
680                 + "\ncategoriaID: " + categoriaID
681                 + "\nAlbum      : " + nmAlbum
682                 + "\nDescricao  : " + descricao
683                 + "\ndtInsercao : " + dtInsercao
684                 + "\nnum.fotos  : " + fotos.size()
685                 + "\n--------------------------------------";
686 
687         while (iter.hasNext()) {
688             PhotoVO f = iter.next();
689             msg = msg + "\n" + f.toString() + "\n--------------------------------------";
690         }
691 
692         return msg;
693     }
694 
695     /**
696      * Retorna uma String contendo todos os dados do albúm no formato XML.
697      *
698      * @return Retorna dados do albúm.
699      */
700     public String toXML() {
701 
702         String r = "<?xml version=\"1.0\" encoding=\"ISO8859-1\"?>"
703                 + "\n<album id=\"" + albumID + "\">"
704                 + "\n\t<titulo>" + nmAlbum + "</titulo>"
705                 + "\n\t<categoria id=\"" + categoriaID + "\">" + getCategoria(categoriaID) + "</categoria>"
706                 + "\n\t<descricao>" + descricao + "</descricao>"
707                 + "\n\t<data>" + dtInsercao + "</data>"
708                 + "\n"
709                 + "\n\t<fotos>";
710 
711         Iterator<PhotoVO> iter = fotos.iterator();
712         while (iter.hasNext()) {
713             PhotoVO f = iter.next();
714             r += "\n\t\t<foto id=\"" + f.getFotoid() + "\">"
715                     + "\n\t\t\t<legenda>" + f.getLegenda() + "</legenda>"
716                     + "\n\t\t\t<credito>" + f.getCreditos().getNome() + "</credito>"
717                     + "\n\t\t\t<altura>" + f.getAltura() + "</altura>"
718                     + "\n\t\t\t<largura>" + f.getLargura() + "</largura>"
719                     + "\n\t\t</foto>";
720         }
721 
722         r += "\n\t</fotos>"
723                 + "\n</album>\n";
724 
725         return r;
726 
727     }
728 
729     /**
730      * Retorna uma String contendo todos os dados do albúm no formato js.
731      *
732      * @return Retorna dados do albúm.
733      */
734     public String toJavaScript() {
735 
736         String r = "albumID=" + albumID + ";\n"
737                 + "categoria='" + getCategoria(categoriaID) + "';\n"
738                 + "titulo=" + Util.stringToHtm(nmAlbum) + ";\n"
739                 + "data='" + dtInsercao + "';\n"
740                 + "descricao=" + Util.stringToHtm(descricao) + ";\n\n"
741                 + "fotos = new Array (";
742 
743         Iterator<PhotoVO> iter = fotos.iterator();
744         String cc = "";
745 
746         while (iter.hasNext()) {
747             PhotoVO f = iter.next();
748             r += cc + "\n\tnew Foto(" + f.getFotoid() + "," + Util.stringToHtm(f.getLegenda()) + ",'" + f.getCreditos().getNome() + "')";
749             cc = ",";
750         }
751 
752         r += "\n\t);\n";
753 
754         return r;
755 
756     }
757 
758     @Override
759     public Object clone() throws CloneNotSupportedException {
760         throw new CloneNotSupportedException("Singleton Object");
761     }
762 
763     private Set<IsPhoto> getAllPhotos() {
764         Set<IsPhoto> joined = new HashSet<IsPhoto>();
765         joined.addAll(fotos);
766         joined.addAll(fotosNovas);
767         return joined;
768     }
769 }