Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.app / org.gvsig.app.mainplugin / src / main / java / org / gvsig / app / project / DefaultProject.java @ 45679

History | View | Annotate | Download (45.9 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.app.project;
24

    
25
import java.awt.Color;
26
import java.awt.image.BufferedImage;
27
import java.beans.PropertyChangeEvent;
28
import java.beans.PropertyChangeListener;
29
import java.beans.PropertyChangeSupport;
30
import java.io.File;
31
import java.io.FileInputStream;
32
import java.io.FileNotFoundException;
33
import java.io.FileOutputStream;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import java.io.OutputStream;
37
import java.io.Serializable;
38
import java.text.DateFormat;
39
import java.text.MessageFormat;
40
import java.text.SimpleDateFormat;
41
import java.util.ArrayList;
42
import java.util.Collections;
43
import java.util.Date;
44
import java.util.HashMap;
45
import java.util.HashSet;
46
import java.util.Iterator;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.Set;
50
import java.util.zip.ZipEntry;
51
import java.util.zip.ZipException;
52
import java.util.zip.ZipFile;
53
import java.util.zip.ZipOutputStream;
54
import javax.imageio.ImageIO;
55
import javax.swing.JOptionPane;
56
import org.apache.commons.io.FileUtils;
57
import org.apache.commons.io.FilenameUtils;
58
import org.apache.commons.io.IOUtils;
59

    
60
import org.cresques.cts.IProjection;
61

    
62
import org.gvsig.andami.PluginServices;
63
import org.gvsig.andami.ui.mdiManager.IWindow;
64
import org.gvsig.andami.ui.mdiManager.MDIManager;
65
import org.gvsig.andami.ui.mdiManager.SingletonWindow;
66
import org.gvsig.andami.ui.mdiManager.WindowInfo;
67
import org.gvsig.app.ApplicationLocator;
68
import org.gvsig.app.ApplicationManager;
69
import org.gvsig.app.extension.ProjectExtension;
70
import org.gvsig.app.extension.Version;
71
import org.gvsig.app.project.ProjectManager.NewProjectEvent;
72
import org.gvsig.app.project.documents.AbstractDocument;
73
import org.gvsig.app.project.documents.Document;
74
import org.gvsig.app.project.documents.exceptions.SaveException;
75
import org.gvsig.app.project.documents.gui.IDocumentWindow;
76
import org.gvsig.app.project.documents.gui.ProjectWindow;
77
import org.gvsig.app.project.documents.view.DefaultViewDocument;
78
import org.gvsig.app.project.documents.view.ViewManager;
79
import org.gvsig.fmap.mapcontext.MapContext;
80
import org.gvsig.fmap.mapcontext.layers.ExtendedPropertiesHelper;
81
import org.gvsig.fmap.mapcontext.layers.FLayer;
82
import org.gvsig.fmap.mapcontext.layers.FLayers;
83
import org.gvsig.tools.ToolsLocator;
84
import org.gvsig.tools.dynobject.DynStruct;
85
import org.gvsig.tools.i18n.I18nManager;
86
import org.gvsig.tools.observer.ObservableHelper;
87
import org.gvsig.tools.observer.Observer;
88
import org.gvsig.tools.persistence.PersistenceManager;
89
import org.gvsig.tools.persistence.Persistent;
90
import org.gvsig.tools.persistence.PersistentContext;
91
import org.gvsig.tools.persistence.PersistentState;
92
import org.gvsig.tools.persistence.exception.PersistenceException;
93
import org.gvsig.tools.swing.api.ToolsSwingLocator;
94
import org.gvsig.tools.swing.api.threadsafedialogs.ThreadSafeDialogsManager;
95
import org.gvsig.utils.StringUtilities;
96

    
97
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
99

    
100
/**
101
 * Clase que representa un proyecto de gvSIG
102
 *
103
 */
104
public class DefaultProject implements Serializable, PropertyChangeListener,
105
        Project {
106

    
107
    private Logger LOG = LoggerFactory.getLogger(DefaultProject.class);
108
    /**
109
     * @deprecated see ApplicationLocator.getManager().getVersion()
110
     */
111
    public static String VERSION = Version.format();
112

    
113
    private ExtendedPropertiesHelper propertiesHelper = new ExtendedPropertiesHelper();
114

    
115
    private ObservableHelper observableHelper = new ObservableHelper();
116

    
117
    /**
118
     *
119
     */
120
    private static final long serialVersionUID = -4449622027521773178L;
121

    
122
    private static final Logger logger = LoggerFactory.getLogger(Project.class);
123

    
124
    private static ProjectPreferences preferences = new ProjectPreferences();
125

    
126
    /**
127
     * Index used by the generator of unique names of documents.
128
     */
129
    private Map<String, Integer> nextDocumentIndexByType = new HashMap<String, Integer>();
130

    
131
    private PropertyChangeSupport change;
132

    
133
    private boolean modified = false;
134

    
135
    private String name = null;
136

    
137
    private String creationDate = null;
138

    
139
    private String modificationDate = null;
140

    
141
    private String owner = null;
142

    
143
    private String comments = null;
144

    
145
    private Color selectionColor = null;
146

    
147
    private List<Document> documents = null;
148

    
149
    private List<ProjectExtent> extents = null;
150

    
151
    private IProjection projection;
152

    
153
    private File fname = null;
154

    
155
    private Set<String> unloadedObjects;
156
    private PersistenceException loadErrors = null;
157

    
158
    /**
159
     * Creates a new Project object.
160
     */
161
    DefaultProject() {
162
        this.change = new PropertyChangeSupport(this);
163
        this.clean();
164
        ProjectManager.getInstance().notifyProjectEvent(new NewProjectEvent() {
165
            @Override
166
            public Project getProject() {
167
                return DefaultProject.this;
168
            }
169
        });
170
    }
171

    
172
    protected void clean() {
173
        this.owner = "";
174
        this.comments = "";
175
        this.name = PluginServices.getText(this, "untitled");
176
        this.creationDate = DateFormat.getDateInstance().format(new Date());
177
        this.modificationDate = this.creationDate;
178

    
179
        this.documents = new ArrayList<Document>();
180
        this.extents = new ArrayList<ProjectExtent>();
181

    
182
        this.setSelectionColor(getPreferences().getDefaultSelectionColor());
183

    
184
        this.projection = null; // se inicializa en el getProjection()
185
    }
186

    
187
    public static ProjectPreferences getPreferences() {
188
        return preferences;
189
    }
190

    
191
    @Override
192
    public void propertyChange(PropertyChangeEvent evt) {
193
        change.firePropertyChange(evt);
194
    }
195

    
196
    @Override
197
    public synchronized void addPropertyChangeListener(
198
            PropertyChangeListener arg0) {
199
        change.addPropertyChangeListener(arg0);
200
    }
201

    
202
    @Override
203
    public void removePropertyChangeListener(PropertyChangeListener listener) {
204
        change.removePropertyChangeListener(listener);
205
    }
206
    
207
    /**
208
     * Return the creation date of the project
209
     *
210
     * @return
211
     */
212
    @Override
213
    public String getCreationDate() {
214
        return creationDate;
215
    }
216

    
217
    protected void setCreationDate(String creationDate) {
218
        this.creationDate = creationDate;
219
        change.firePropertyChange("setCreationDate", null, null);
220
    }
221

    
222
    @Override
223
    public Document createDocument(String type) {
224
        logger.info("createDocument('{}')", type);
225
        return ProjectManager.getInstance().createDocument(type);
226
    }
227

    
228
    /**
229
     * Return the name of the project
230
     *
231
     * @return
232
     */
233
    @Override
234
    public String getName() {
235
        return name;
236
    }
237

    
238
    /**
239
     * Set the name of he project.
240
     *
241
     * @param string
242
     */
243
    @Override
244
    public void setName(String name) {
245
        this.name = name;
246
        change.firePropertyChange("setName", null, null);
247
    }
248

    
249
    /**
250
     * Return the comments associateds with the project
251
     *
252
     * @return comments
253
     */
254
    @Override
255
    public String getComments() {
256
        return comments;
257
    }
258

    
259
    /**
260
     * Set the comments associateds with the project
261
     *
262
     * @param comments as string
263
     */
264
    @Override
265
    public void setComments(String string) {
266
        comments = string;
267
        change.firePropertyChange("setComments", null, null);
268
    }
269

    
270
    /**
271
     * Retuen the modification date of the project.
272
     *
273
     * @return modification date as string
274
     */
275
    @Override
276
    public String getModificationDate() {
277
        return modificationDate;
278
    }
279

    
280
    protected void setModificationDate(String string) {
281
        modificationDate = string;
282
        change.firePropertyChange("setModificationDate", null, null);
283
    }
284

    
285
    /**
286
     * Return the author of the project,
287
     *
288
     * @return author as string
289
     */
290
    @Override
291
    public String getOwner() {
292
        return owner;
293
    }
294

    
295
    /**
296
     * Sets the author of the project
297
     *
298
     * @param author name as string
299
     */
300
    @Override
301
    public void setOwner(String owner) {
302
        this.owner = owner;
303
        change.firePropertyChange("setOwner", null, null);
304
    }
305

    
306
    /**
307
     * Obtiene el color de selecci�n que se usar� en el proyecto
308
     *
309
     * @return
310
     */
311
    @Override
312
    public Color getSelectionColor() {
313
        if (selectionColor == null) {
314
            selectionColor = getPreferences().getDefaultSelectionColor();
315
        }
316
        return selectionColor;
317
    }
318

    
319
    /**
320
     * Sets the selecction color
321
     *
322
     * @param selection color as string
323
     */
324
    @Override
325
    public void setSelectionColor(String selectionColor) {
326
        this.setSelectionColor(StringUtilities.string2Color(selectionColor));
327
    }
328

    
329
    /**
330
     * Sets the selecction color
331
     *
332
     * @param selection color as Color
333
     */
334
    @Override
335
    public void setSelectionColor(Color selectionColor) {
336
        this.selectionColor = selectionColor;
337
        MapContext.setSelectionColor(selectionColor);
338
        change.firePropertyChange("selectionColor", null, selectionColor);
339
    }
340

    
341
    @Override
342
    public IProjection getProjection() {
343
        if (projection == null) {
344
            projection = getPreferences().getDefaultProjection();
345
        }
346
        return projection;
347
    }
348

    
349
    @Override
350
    public void setProjection(IProjection projection) {
351
        this.projection = projection;
352
    }
353

    
354
    /**
355
     * Sets the modified state of project.
356
     *
357
     * Can't set to not modified.
358
     *
359
     * @param modified as boolean
360
     */
361
    @Override
362
    public void setModified(boolean modified) {
363
        this.modified = modified;
364
        if (modified == false) {
365
            List<Document> documents = this.getDocuments();
366
            for (int i = 0; i < documents.size(); i++) {
367
                documents.get(i).setModified(false);
368
            }
369
        }
370
    }
371

    
372
    @Override
373
    public boolean hasChanged() {
374
        if( modified ) {
375
            return true;
376
        }
377
        if (this.getDocuments().isEmpty() ) {
378
            return false;
379
        }
380
        for (Document document : this.getDocuments()) {
381
            if( document.isTemporary()) {
382
                continue;
383
            }
384
            if( document.isModified() ) {
385
                return true;
386
            }
387
        }
388
        return false;
389
    }
390

    
391
    /**
392
     * Return a list of documents in the project.
393
     *
394
     * @return documents as List of IProjectDocument
395
     */
396
    @Override
397
    public List<Document> getDocuments() {
398
        return Collections.unmodifiableList(documents);
399
    }
400

    
401
    /**
402
     * Return a list with all documents of especified type.
403
     *
404
     * @param type of document
405
     *
406
     * @return List of IProjectDocument
407
     */
408
    @Override
409
    public List<Document> getDocuments(String type) {
410
        List<Document> docs = new ArrayList<Document>();
411
        if (type != null) {
412
            for (Document document : this.documents) {
413
                if (type.equalsIgnoreCase(document.getTypeName())) {
414
                    docs.add(document);
415
                }
416
            }
417
        }
418
        return Collections.unmodifiableList(docs);
419
    }
420

    
421
    /**
422
     * Adds a document to the project
423
     *
424
     * @param document as Document
425
     */
426
    @Override
427
    public void add(Document document) {
428
        this.addDocument(document);
429
    }
430

    
431
    @Override
432
    public void addDocument(Document document) {
433
        logger.info("add('{}')", document.toString());
434

    
435
        if (notifyObservers(ProjectNotification.BEFORE_ADD_DOCUMENT, document).isProcessCanceled()) {
436
            return;
437
        }
438
        document.addPropertyChangeListener(this);
439
        document.setProject(this);
440
        document.setName(this.getUniqueNameForDocument(document.getTypeName(),
441
                document.getName()));
442
        documents.add(document);
443
        document.afterAdd();
444
        this.setModified(true);
445
        notifyObservers(ProjectNotification.AFTER_ADD_DOCUMENT, document);
446
        change.firePropertyChange("addDocument", null, document);
447
    }
448

    
449
    @Override
450
    public void remove(Document doc) {
451
        this.removeDocument(doc);
452
    }
453

    
454
    @Override
455
    public void removeDocument(Document doc) {
456
        logger.info("remove('{}')", doc.toString());
457
        if (notifyObservers(ProjectNotification.BEFORE_REMOVE_DOCUMENT, doc).isProcessCanceled()) {
458
            return;
459
        }
460
        documents.remove(doc);
461
        this.setModified(true);
462
        change.firePropertyChange("delDocument", doc, null);
463
        doc.afterRemove();
464
        notifyObservers(ProjectNotification.AFTER_REMOVE_DOCUMENT, doc);
465
    }
466

    
467
    @Override
468
    public Iterator<Document> iterator() {
469
        return documents.iterator();
470
    }
471

    
472
    @Override
473
    public boolean isEmpty() {
474
        return documents.isEmpty();
475
    }
476

    
477
    /**
478
     * Return the view that contains the especified layer.
479
     *
480
     * @param layer
481
     *
482
     * @return name of the view that contains the layer
483
     *
484
     * @throws RuntimeException Si la capa que se pasa como par�metro no se
485
     * encuentra en ninguna vista
486
     */
487
    @Override
488
    public String getViewName(FLayer layer) {
489
        List<Document> views = getDocuments(ViewManager.TYPENAME);
490
        for (int v = 0; v < views.size(); v++) {
491
            DefaultViewDocument pView = (DefaultViewDocument) views.get(v);
492
            FLayers layers = pView.getMapContext().getLayers();
493
            if (isView(layers, layer)) {
494
                return pView.getName();
495
            }
496
        }
497

    
498
        throw new RuntimeException(MessageFormat.format(
499
                "The layer '{1}' is not in a view", layer.getName()));
500
    }
501

    
502
    private boolean isView(FLayers layers, FLayer layer) {
503
        for (int i = 0; i < layers.getLayersCount(); i++) {
504
            if (layers.getLayer(i) instanceof FLayers) {
505
                if (isView((FLayers) layers.getLayer(i), layer)) {
506
                    return true;
507
                }
508
            }
509
            if (layers.getLayer(i) == layer) {
510
                return true;
511
            }
512
        }
513
        return false;
514
    }
515

    
516
    @Override
517
    public void addExtent(ProjectExtent arg1) {
518
        extents.add(arg1);
519
        change.firePropertyChange("addExtent", null, null);
520
    }
521

    
522
    @Override
523
    public ProjectExtent removeExtent(int arg0) {
524
        change.firePropertyChange("delExtent", null, null);
525
        return extents.remove(arg0);
526
    }
527

    
528
    @Override
529
    public ProjectExtent[] getExtents() {
530
        return (ProjectExtent[]) extents.toArray(new ProjectExtent[0]);
531
    }
532

    
533
    /**
534
     * Obtiene un documento a partir de su nombre y el nombre de registro en el
535
     * pointExtension, este �ltimo se puede obtener del
536
     * Project****Factory.registerName.
537
     *
538
     * @param name Nombre del documento
539
     * @param type nombre de registro en el extensionPoint
540
     *
541
     * @return Documento
542
     */
543
    @Override
544
    public Document getDocument(String name, String type) {
545
        if (type == null) {
546
            for (int i = 0; i < documents.size(); i++) {
547
                Document document = documents.get(i);
548
                if ( name.equalsIgnoreCase(document.getName())) {
549
                    return document;
550
                }
551
            }
552
        } else {
553
            for (int i = 0; i < documents.size(); i++) {
554
                Document document = documents.get(i);
555
                if (type.equalsIgnoreCase(document.getTypeName())
556
                        && name.equalsIgnoreCase(document.getName())) {
557
                    return document;
558
                }
559
            }
560
        }
561
        return null;
562
    }
563

    
564
    @Override
565
    public Document getDocument(String name) {
566
        return this.getDocument(name, null);
567
    }
568

    
569
    public String getUniqueNameForDocument(String type, String name) {
570
        Document document = getDocument(name, type);
571
        if (document == null) {
572
            return name;
573
        }
574

    
575
        String newName = null;
576
        int num = this.getNextDocumentIndex(type);
577
        while (document != null) {
578
            newName = name + " - " + num++;
579
            document = getDocument(newName, type);
580
        }
581
        this.setNextDocumentIndex(type, num);
582
        return newName;
583
    }
584

    
585
    private int getNextDocumentIndex(String type) {
586
        if (nextDocumentIndexByType.get(type) == null) {
587
            nextDocumentIndexByType.put(type, new Integer(1));
588
            return 1;
589
        }
590
        return nextDocumentIndexByType.get(type).intValue();
591
    }
592

    
593
    private void setNextDocumentIndex(String type, int newIndex) {
594
        if (nextDocumentIndexByType.get(type) == null) {
595
            nextDocumentIndexByType.put(type, new Integer(newIndex));
596
        } else {
597
            nextDocumentIndexByType.put(type, new Integer(newIndex));
598
        }
599
    }
600

    
601
    @Override
602
    public void saveState(File out) throws PersistenceException {
603
        FileOutputStream fout;
604
        if (notifyObservers(ProjectNotification.BEFORE_SAVE_TO_FILE, out).isProcessCanceled()) {
605
            return;
606
        }
607
        try {
608
            fout = new FileOutputStream(out);
609
            saveState(fout, new File(out.getParent()));
610
        } catch (FileNotFoundException e) {
611
            createProjectCopy(true);
612
            throw new PersistenceException(e);
613
        }
614
        if( !isValidZIP(out) ) {
615
            throw new PersistenceException(new ZipException());
616
        }
617
        this.fname = out;
618
        //sacar copia al bak
619
        createProjectCopy(false);
620
        notifyObservers(ProjectNotification.AFTER_SAVE_TO_FILE, out);
621
    }
622

    
623
    boolean isValidZIP(final File file) {
624
        ZipFile zipfile = null;
625
        try {
626
            zipfile = new ZipFile(file);
627
            return true;
628
        } catch (IOException e) {
629
            //sacar copia. 
630
            return false;
631
        } finally {
632
            try {
633
                if (zipfile != null) {
634
                    zipfile.close();
635
                    zipfile = null;
636
                }
637
            } catch (IOException e) {
638
            }
639
        }
640
    }
641
   
642
    @Override
643
    public File getFile() {
644
        return this.fname;
645
    }
646

    
647
    @Deprecated
648
    @Override
649
    public void saveState(OutputStream out) throws PersistenceException {
650
        saveState(out, null);
651
    }
652
    
653
    @Override
654
    public void saveState(File file, BufferedImage preview) {
655
        FileOutputStream fout=null;
656
        ZipOutputStream zout=null;
657
        try {
658
            fout = new FileOutputStream(file);
659
            zout = new ZipOutputStream(fout);
660
            this.saveState(zout, file.getParentFile());
661

    
662
            zout.putNextEntry(new ZipEntry("preview.jpg"));
663
            try {
664
                ImageIO.write(preview, "jpg", zout);
665
            } catch (IOException ex) {
666
                LOG.warn("Can't save preview image'.", ex);
667
            }
668
            IOUtils.closeQuietly(zout);
669
            IOUtils.closeQuietly(fout);
670

    
671
            if( !isValidZIP(file) ) {
672
                throw new ZipException("Invalid project file '"+file.getAbsolutePath()+"'");
673
            }
674
            this.fname = file;
675
            if (file.exists()) {
676
                createProjectCopy(false);
677
            }
678
            I18nManager i18n = ToolsLocator.getI18nManager();
679
            ThreadSafeDialogsManager dialog = ToolsSwingLocator.getThreadSafeDialogsManager();
680
            dialog.message(
681
                    i18n.getTranslation("wrote_project"), 
682
                    JOptionPane.INFORMATION_MESSAGE
683
            );
684
        } catch (Exception ex) {
685
            createProjectCopy(true);
686
            throw new RuntimeException("Can't write project in '" + file.getAbsolutePath() + ".", ex);
687
        } finally {
688
            IOUtils.closeQuietly(zout);
689
            IOUtils.closeQuietly(fout);
690

    
691
        }
692
    }
693
    
694
    @Override
695
    public void saveState(OutputStream out, File rootFolder)
696
            throws PersistenceException {
697
        if (notifyObservers(ProjectNotification.BEFORE_SAVE_TO_STREAM, rootFolder).isProcessCanceled()) {
698
            return;
699
        }
700
        PersistenceManager manager = ToolsLocator.getPersistenceManager();
701
        PersistentState state = null;
702
        state = manager.getState(this, true);
703
        try {
704
            if (rootFolder != null) {
705
                state.relativizeFiles(rootFolder);
706
            }
707
        } catch (Exception ex) {
708
            state.getContext().addError(ex);
709
        }
710
        manager.saveState(state, out, true);
711
        if (state.getContext().getErrors() != null) {
712
            createProjectCopy(true);
713
            throw state.getContext().getErrors();
714
        }
715
        this.fname = null;
716
        notifyObservers(ProjectNotification.AFTER_SAVE_TO_STREAM, rootFolder);
717
    }
718
    
719
    private File getNameFileForProject(File actualProjectFile, boolean error) {
720
        // if there is a error, creates a file with a unique timecode
721
        // not error: return path with .gvsproj.bak extension that could exists
722
               
723
        if (actualProjectFile==null) {
724
            LOG.info("Doesn't have previous project to create a bak");
725
            return null;
726
        }
727
        String noExt = FilenameUtils.removeExtension(actualProjectFile.getAbsolutePath());
728
        String timeCode = new SimpleDateFormat("_MMddHHmmss").format(new Date());
729
        String ext = ".gvsproj.bak";
730

    
731
        int code = 0;
732
        if (noExt.length() > 230) {
733
            noExt = noExt.substring(0, 229);
734
        }
735
        
736
        File bakProjectFile;
737
        if (error) {
738
            bakProjectFile = new File(noExt + timeCode + ext);
739
            while (bakProjectFile.exists()) {
740
                code = code + 1;
741
                bakProjectFile = new File(noExt + timeCode + "_" + code + ext);
742
            }
743
        } else {
744
            bakProjectFile = new File(noExt + ext);
745
        }
746
        return bakProjectFile;
747
    }
748
    public File createProjectCopy(boolean error) {
749
        File actualProjectFile = ApplicationLocator.getProjectManager().getCurrentProject().getFile();
750
        File bakProjectFile = getNameFileForProject(actualProjectFile, error);
751
        if (bakProjectFile==null){
752
            return null;
753
        }
754
        if (!error){
755
            try {
756
                if (bakProjectFile.exists()){
757
                    FileUtils.deleteQuietly(bakProjectFile);
758
                }
759
            } catch (Exception ex) {
760
                LOG.error("Not possible to delete file", ex);
761
            }
762
            try {
763
                FileUtils.copyFile(actualProjectFile, bakProjectFile);
764
            } catch (IOException ex) {
765
                LOG.error("Can't create bak copy from project", ex);
766
            }
767
        } else {
768
            File bakProjectFileOld = getNameFileForProject(actualProjectFile, false);
769
            if (bakProjectFileOld.exists()) {
770
                try {
771
                    FileUtils.moveFile(bakProjectFileOld, bakProjectFile);
772
                } catch (IOException ex) {
773
                    LOG.error("Can't rename file", ex);
774
                }
775
            } else {
776
                LOG.warn("Can't find bak project. Probably doesn't exist or already has been renamed");
777
            }
778
        }
779
        
780
        return bakProjectFile;
781
    }
782

    
783
    @Deprecated
784
    @Override
785
    public void loadState(InputStream in) {
786
        loadState(in, null);
787
    }
788

    
789
    public void loadState(InputStream in, File rootFolder) {
790
        if (notifyObservers(ProjectNotification.BEFORE_LOAD_FROM_STREAM, rootFolder).isProcessCanceled()) {
791
            return;
792
        }
793
        PersistenceManager manager = ToolsLocator.getPersistenceManager();
794
        try {
795
            PersistentContext context = manager.getNewContext();
796
            context.setCollectErrors(true);
797
            PersistentState state = manager.loadState(in, context);
798
            try {
799
                if (rootFolder != null) {
800
                    state.derelativizeFiles(rootFolder);
801
                }
802
            } catch (Exception ex) {
803
                state.getContext().addError(ex);
804
            }
805
            this.loadFromState(state);
806
            this.unloadedObjects = getUnloadedObjects(state.getContext());
807
            this.loadErrors = state.getContext().getErrors();
808

    
809
        } catch (PersistenceException e) {
810
            LOG.info("Can't load project to stream", e);
811
        }
812
        this.fname = null;
813
        notifyObservers(ProjectNotification.AFTER_LOAD_FROM_STREAM, rootFolder);
814

    
815
    }
816

    
817

    
818

    
819
    /**
820
     * @param context
821
     * @return
822
     */
823
    private Set<String> getUnloadedObjects(PersistentContext context) {
824
        Set unloadedObjects = new HashSet();
825

    
826
        Iterator statesIterator = context.iterator();
827
        String className = null;
828

    
829
        while (statesIterator.hasNext()) {
830
            PersistentState aState = (PersistentState) statesIterator.next();
831
            try {
832
                className = aState.getTheClassName();
833
                DynStruct definition = aState.getDefinition();
834
                if (definition == null) {
835
                    unloadedObjects.add(className);
836
                }
837
            } catch (Throwable e) {
838
                // do nothing
839
            }
840
        }
841

    
842
        if(unloadedObjects.isEmpty()){
843
            return null;
844
        }
845

    
846
        return unloadedObjects;
847
    }
848

    
849
    @Override
850
    public Set<String> getUnloadedObjects() {
851
        return this.unloadedObjects;
852
    }
853

    
854
    @Override
855
    public List<Exception> getLoadErrors() {
856
        return this.loadErrors;
857
    }
858
    
859
    @Override
860
    public void loadState(File in) {
861
        if (notifyObservers(ProjectNotification.BEFORE_LOAD_FROM_FILE, in).isProcessCanceled()) {
862
            return;
863
        }
864
        FileInputStream fin;
865
        try {
866
            fin = new FileInputStream(in);
867
            loadState(fin, new File(in.getParent()));
868
        } catch (FileNotFoundException e) {
869
            LOG.info("Can't load project to stream", e);
870
        }
871
        if ("bak".equals(FilenameUtils.getExtension(in.getAbsolutePath()))) {
872
            this.fname =  null;
873
        } else {
874
            this.fname = in;
875
        }
876
        notifyObservers(ProjectNotification.AFTER_LOAD_FROM_FILE, in);
877
    }
878

    
879
    //@SuppressWarnings("unchecked")
880
    @Override
881
    public void loadFromState(PersistentState state)
882
            throws PersistenceException {
883
        this.clean();
884
        PersistentContext context = state.getContext();
885
        
886
        notifyObservers(ProjectNotification.BEFORE_LOAD_FROM_STATE, state);
887

    
888
        this.setComments(state.getString("comments"));
889
        this.setCreationDate(state.getString("creationDate"));
890
        this.setModificationDate(state.getString("modificationDate"));
891
        this.setName(state.getString("name"));
892
        this.setOwner(state.getString("owner"));
893
        this.setSelectionColor((Color) state.get("selectionColor"));
894
        this.setProjection((IProjection) state.get("projection"));
895

    
896
        this.propertiesHelper = (ExtendedPropertiesHelper) state.get("propertiesHelper");
897

    
898
        List<ProjectExtent> extents = (List<ProjectExtent>) state
899
                .get("extents");
900
        for (int i = 0; i < extents.size(); i++) {
901
            this.addExtent(extents.get(i));
902
        }
903

    
904
        List<AbstractDocument> documents = (List<AbstractDocument>) state
905
                .get("documents");
906
        for (int i = 0; i < documents.size(); i++) {
907
            AbstractDocument doc = documents.get(i);
908
            if( doc != null ) {
909
                this.add(doc);
910
            }
911
        }
912

    
913
        try {
914
            List<DocumentWindowInfo> persistentWindows = (List<DocumentWindowInfo>) state.get("documentWindowsInformation");
915

    
916
            for (int i = 0; i < persistentWindows.size(); i++) {
917
                try {
918
                    DocumentWindowInfo persistentWindow = persistentWindows.get(i);
919
                    String docName = persistentWindow.getDocumentName();
920
                    String docType = persistentWindow.getDocumentType();
921
                    Document doc = this.getDocument(docName, docType);
922
                    IWindow win = doc.getFactory().getMainWindow(doc);
923
                    if(win!=null){
924
                        win.getWindowInfo().setWindowInfo(persistentWindow.getWindowInfo());
925
                        PluginServices.getMDIManager().addWindow(win);
926
                    }
927
                } catch(Exception ex) {
928
                    if( !context.getCollectErrors() ) {
929
                        throw ex;
930
                    }
931
                    context.addError(ex);
932
                }
933
            }
934

    
935
            if (state.hasValue("projectWindowInfo")) {
936
                WindowInfo projectWindowInfo = (WindowInfo) state.get("projectWindowInfo");
937
                ProjectExtension pe = (ProjectExtension) PluginServices.getExtension(org.gvsig.app.extension.ProjectExtension.class);
938
                pe.setProject(this);
939
                pe.showProjectWindow(projectWindowInfo);
940
            }
941
        } catch(Exception ex) {
942
            if( !context.getCollectErrors() ) {
943
                throw ex;
944
            }
945
            context.addError(ex);
946
        }
947
        notifyObservers(ProjectNotification.AFTER_LOAD_FROM_STATE, state);
948
    }
949

    
950
    @Override
951
    public void saveToState(PersistentState state) throws PersistenceException {
952
        state.set("version", VERSION);
953
        state.set("comments", getComments());
954
        state.set("creationDate", this.getCreationDate());
955

    
956
        state.set("modificationDate", this.getModificationDate());
957
        state.set("name", this.getName());
958
        state.set("owner", this.getOwner());
959
        state.set("selectionColor", this.getSelectionColor());
960

    
961
        state.set("projection", this.getProjection());
962

    
963
        state.set("extents", this.extents);
964
        List<Document> docs = this.getDocuments();
965
        List<Document> noTempDocs = new ArrayList<>();
966
        for (Document document : docs) {
967
            if(!document.isTemporary()){
968
                noTempDocs.add(document);
969
            }
970
        }
971
        state.set("documents", noTempDocs);
972

    
973
        state.set("propertiesHelper",propertiesHelper);
974

    
975
        List<DocumentWindowInfo> persistentWindows = new ArrayList<>();
976
        MDIManager mdiMan = PluginServices.getMDIManager();
977
        IWindow[] windows = mdiMan.getOrderedWindows();
978
        for (int i = windows.length - 1; i >= 0; i--) {
979
            IWindow window = windows[i];
980
            if (window instanceof IDocumentWindow) {
981
                WindowInfo wi = mdiMan.getWindowInfo(window);
982
                DocumentWindowInfo dwi = new DocumentWindowInfo(
983
                        wi,
984
                        ((IDocumentWindow) window).getDocument().getTypeName(),
985
                        ((IDocumentWindow) window).getDocument().getName());
986
                persistentWindows.add(dwi);
987
            } else if (window instanceof ProjectWindow) {
988
                state.set("projectWindowInfo", mdiMan.getWindowInfo(window));
989
            }
990
        }
991
        state.set("documentWindowsInformation", persistentWindows);
992

    
993
    }
994

    
995
    @Override
996
    public Object getProperty(Object key) {
997
        return this.propertiesHelper.getProperty(key);
998
    }
999

    
1000
    @Override
1001
    public void setProperty(Object key, Object obj) {
1002
        this.propertiesHelper.setProperty(key, obj);
1003
    }
1004

    
1005
    @Override
1006
    public Map getExtendedProperties() {
1007
        return this.propertiesHelper.getExtendedProperties();
1008
    }
1009

    
1010
    public static class DocumentWindowInfo implements Persistent {
1011

    
1012
        public static final String PERSISTENCE_DEFINITION_NAME = "DocumentWindowInfo";
1013

    
1014
        private WindowInfo windowInfo;
1015
        private String documentType;
1016
        private String documentName;
1017

    
1018
        public DocumentWindowInfo() {
1019
        }
1020

    
1021
        DocumentWindowInfo(WindowInfo wi, String docType, String docName) {
1022
            windowInfo = wi;
1023
            documentType = docType;
1024
            documentName = docName;
1025
        }
1026

    
1027
        public WindowInfo getWindowInfo() {
1028
            return windowInfo;
1029
        }
1030

    
1031
        public String getDocumentType() {
1032
            return documentType;
1033
        }
1034

    
1035
        public String getDocumentName() {
1036
            return documentName;
1037
        }
1038

    
1039
        public void saveToState(PersistentState state)
1040
                throws PersistenceException {
1041
            state.set("windowInfo", this.windowInfo);
1042
            state.set("documentType", this.documentType);
1043
            state.set("documentName", this.documentName);
1044
        }
1045

    
1046
        public void loadFromState(PersistentState state)
1047
                throws PersistenceException {
1048
            this.windowInfo = (WindowInfo) state.get("windowInfo");
1049
            this.documentType = state.getString("documentType");
1050
            this.documentName = state.getString("documentName");
1051
        }
1052

    
1053
        public static void registerPersistent() {
1054
            PersistenceManager manager = ToolsLocator.getPersistenceManager();
1055
            DynStruct definition = manager.getDefinition(PERSISTENCE_DEFINITION_NAME);
1056
            if (definition == null) {
1057
                definition = manager.addDefinition(
1058
                        DocumentWindowInfo.class,
1059
                        PERSISTENCE_DEFINITION_NAME,
1060
                        "DocumentWindowInfo persistence definition",
1061
                        null,
1062
                        null
1063
                );
1064
                definition.addDynFieldObject("windowInfo").setMandatory(true).setClassOfValue(WindowInfo.class);
1065
                definition.addDynFieldString("documentType").setMandatory(true);
1066
                definition.addDynFieldString("documentName").setMandatory(true);
1067
            }
1068

    
1069
        }
1070
    }
1071

    
1072
    public static void registerPersistent() {
1073
        AbstractDocument.registerPersistent();
1074
        DocumentWindowInfo.registerPersistent();
1075
        ProjectExtent.registerPersistent();
1076

    
1077
        PersistenceManager manager = ToolsLocator.getPersistenceManager();
1078
        DynStruct definition = manager.addDefinition(DefaultProject.class,
1079
                "Project", "Project Persistence definition", null, null);
1080
        definition.addDynFieldString("version").setMandatory(true);
1081
        definition.addDynFieldString("comments").setMandatory(true);
1082
        definition.addDynFieldString("creationDate").setMandatory(true);
1083
        definition.addDynFieldString("modificationDate").setMandatory(true);
1084
        definition.addDynFieldString("name").setMandatory(true);
1085
        definition.addDynFieldString("owner").setMandatory(true);
1086

    
1087
        definition.addDynFieldObject("selectionColor")
1088
                .setClassOfValue(Color.class).setMandatory(true);
1089
        definition.addDynFieldObject("projection")
1090
                .setClassOfValue(IProjection.class).setMandatory(true);
1091

    
1092
        definition.addDynFieldList("extents")
1093
                .setClassOfItems(ProjectExtent.class).setMandatory(true);
1094

    
1095
        definition.addDynFieldList("documents").setClassOfItems(Document.class)
1096
                .setMandatory(true);
1097

    
1098
        definition.addDynFieldObject("projectWindowInfo").setClassOfValue(WindowInfo.class).setMandatory(false);
1099

    
1100
        definition.addDynFieldList("documentWindowsInformation").setClassOfItems(WindowInfo.class).setMandatory(false);
1101

    
1102

    
1103
        definition.addDynFieldObject("propertiesHelper").setClassOfValue(ExtendedPropertiesHelper.class)
1104
                        .setMandatory(false);
1105

    
1106
    }
1107

    
1108
    /**
1109
     * @deprecated use getPreferences().setDefaultSelectionColor()
1110
     */
1111
    public static void setDefaultSelectionColor(Color color) {
1112
        getPreferences().setDefaultSelectionColor(color);
1113
    }
1114

    
1115
    /**
1116
     * @deprecated use getPreferences().getDefaultSelectionColor()
1117
     */
1118
    public static Color getDefaultSelectionColor() {
1119
        return getPreferences().getDefaultSelectionColor();
1120
    }
1121

    
1122
    /**
1123
     * @deprecated use getPreferences().getDefaultMapUnits()
1124
     */
1125
    public static int getDefaultMapUnits() {
1126
        return getPreferences().getDefaultMapUnits();
1127
    }
1128

    
1129
    /**
1130
     * @deprecated use getPreferences().getDefaultDistanceUnits()
1131
     */
1132
    public static int getDefaultDistanceUnits() {
1133
        return getPreferences().getDefaultDistanceUnits();
1134
    }
1135

    
1136
    /**
1137
     * @deprecated use getPreferences().getDefaultDistanceArea()
1138
     */
1139
    public static int getDefaultDistanceArea() {
1140
        return getPreferences().getDefaultDistanceArea();
1141
    }
1142

    
1143
    /**
1144
     * @deprecated use getPreferences().setDefaultMapUnits()
1145
     */
1146
    public static void setDefaultMapUnits(int mapUnits) {
1147
        getPreferences().setDefaultMapUnits(mapUnits);
1148
    }
1149

    
1150
    /**
1151
     * @deprecated use getPreferences().setDefaultDistanceUnits()
1152
     */
1153
    public static void setDefaultDistanceUnits(int distanceUnits) {
1154
        getPreferences().setDefaultDistanceUnits(distanceUnits);
1155
    }
1156

    
1157
    /**
1158
     * @deprecated use getPreferences().setDefaultDistanceArea()
1159
     */
1160
    public static void setDefaultDistanceArea(int distanceArea) {
1161
        getPreferences().setDefaultDistanceArea(distanceArea);
1162
    }
1163

    
1164
    /**
1165
     * @deprecated use getPreferences().setDefaultProjection()
1166
     */
1167
    public static void setDefaultProjection(IProjection defaultProjection) {
1168
        getPreferences().setDefaultProjection(defaultProjection);
1169
    }
1170

    
1171
    /**
1172
     * @deprecated use getPreferences().getDefaultProjection()
1173
     */
1174
    public static IProjection getDefaultProjection() {
1175
        return getPreferences().getDefaultProjection();
1176
    }
1177

    
1178
    /**
1179
     * @deprecated see {@link #setSelectionColor(String)}, to be remove in gvSIG
1180
     * 2.1.0
1181
     */
1182
    public void setColor(String color) {
1183
        this.setSelectionColor(StringUtilities.string2Color(color));
1184
    }
1185

    
1186
    /**
1187
     * Return the selection color
1188
     *
1189
     * @return selection color as string
1190
     * @deprecated use {@link #getSelectionColor()}
1191
     */
1192
    public String getColor() {
1193
        return StringUtilities.color2String(selectionColor);
1194
    }
1195

    
1196
    /**
1197
     * Return the list of views of the project
1198
     *
1199
     * @return views as ArrayList of ProjectDocument
1200
     *
1201
     * @deprecated see {@link #getDocumentsByType(String)}
1202
     */
1203
    public List<Document> getViews() {
1204
        return getDocuments(ViewManager.TYPENAME);
1205
    }
1206

    
1207
    /**
1208
     * Add a view to the project
1209
     *
1210
     * @deprecated see {@link #add(AbstractDocument)}
1211
     */
1212
    public void addView(DefaultViewDocument v) {
1213
        add(v);
1214
    }
1215

    
1216
    /**
1217
     * Remove a view of the project
1218
     *
1219
     * @param index of the view as integer
1220
     *
1221
     * @deprecated see {@link #remove(AbstractDocument)}
1222
     */
1223
    public void delView(int i) {
1224
        List<Document> list = getDocuments(ViewManager.TYPENAME);
1225
        remove(list.get(i));
1226
    }
1227

    
1228
    /**
1229
     * @deprecated see {@link #getDocument(String, String)}
1230
     */
1231
    public Document getProjectDocumentByName(String name, String type) {
1232
        return this.getDocument(name, type);
1233
    }
1234

    
1235
    /**
1236
     * @deprecated see {@link #getDocuments(String)}
1237
     */
1238
    public List<Document> getDocumentsByType(String type) {
1239
        return this.getDocuments(type);
1240
    }
1241

    
1242
    /**
1243
     * @deprecated aun por decidir que API darle al copy/paste
1244
     */
1245
    public String exportToXML(AbstractDocument[] selectedItems)
1246
            throws SaveException {
1247
        // FIXME jjdc:hay que decirdir que API darle al copy/paste
1248
        throw new UnsupportedOperationException("This method is not supported");
1249
    }
1250

    
1251
    /**
1252
     * @deprecated aun por decidir que API darle al copy/paste
1253
     */
1254
    public void importFromXML(String sourceString, String docType) {
1255
        // FIXME jjdc:hay que decirdir que API darle al copy/paste
1256
        throw new UnsupportedOperationException("This method is not supported");
1257
    }
1258

    
1259
    /**
1260
     * @deprecated aun por decidir que API darle al copy/paste
1261
     */
1262
    public boolean isValidXMLForImport(String sourceString, String docType) {
1263
        // FIXME jjdc:hay que decirdir que API darle al copy/paste
1264
        throw new UnsupportedOperationException("This method is not supported");
1265
    }
1266

    
1267
    @Override
1268
    public boolean canImportDocuments(String data, String doctype) {
1269
        // TODO Auto-generated method stub
1270
        return false;
1271
    }
1272

    
1273
    @Override
1274
    public String exportDocumentsAsText(List<Document> documents) {
1275
        // TODO Auto-generated method stub
1276
        return null;
1277
    }
1278

    
1279
    @Override
1280
    public void importDocuments(String data, String doctype) {
1281
        // TODO Auto-generated method stub
1282

    
1283
    }
1284

    
1285
    @Override
1286
    public Document getActiveDocument() {
1287
        return this.getActiveDocument((Class<? extends Document>)null);
1288
    }
1289

    
1290
    @Override
1291
    public Document getActiveDocument(String documentTypeName) {
1292
        ApplicationManager application = ApplicationLocator.getManager();
1293

    
1294
        Document document = null;
1295
        IWindow[] windows = application.getUIManager().getOrderedWindows();
1296
        IWindow window = null;
1297
        for (int i = 0; i < windows.length; i++) {
1298
            window = windows[i];
1299
            if (window instanceof SingletonWindow) {
1300
                // Cogemos no la primera ventana, si no la primera
1301
                // ventana de tipo documento (SingletonWindow).
1302
                // Y por si las mosca no es un documento, atrapamos
1303
                // los errores y continuamos si no puede hacer un cast
1304
                // del Model a Document
1305
                try {
1306
                    document = (Document) ((SingletonWindow) window).getWindowModel();
1307
                    if (documentTypeName == null) {
1308
                        return document;
1309
                    }
1310
                    if( document.getTypeName().equalsIgnoreCase(documentTypeName) ) {
1311
                        return document;
1312
                    }
1313
                    if( document instanceof DocumentsContainer ) {
1314
                        Document subdoc = ((DocumentsContainer)document).getActiveDocument(documentTypeName);
1315
                        return subdoc;
1316
                    }
1317

    
1318
                } catch (ClassCastException e) {
1319
                    // Do nothing, skip this window
1320
                }
1321
            }
1322
        }
1323
        return null;
1324
    }
1325

    
1326
    @Override
1327
    public Document getActiveDocument(Class<? extends Document> documentClass) {
1328
        ApplicationManager application = ApplicationLocator.getManager();
1329

    
1330
        Document document = null;
1331
        IWindow[] windows = application.getUIManager().getOrderedWindows();
1332
        IWindow window = null;
1333
        for (int i = 0; i < windows.length; i++) {
1334
            window = windows[i];
1335
            if (window instanceof SingletonWindow && window instanceof IDocumentWindow) {
1336
                // Cogemos no la primera ventana, si no la primera
1337
                // ventana de tipo documento (SingletonWindow).
1338
                // Y por si las mosca no es un documento, atrapamos
1339
                // los errores y continuamos si no puede hacer un cast
1340
                // del Model a Document
1341
                try {
1342
                    document = (Document) ((SingletonWindow) window).getWindowModel();
1343
                    if (documentClass == null) {
1344
                        return document;
1345
                    }
1346
                    if (documentClass.isAssignableFrom(document.getClass())) {
1347
                        return document;
1348
                    }
1349
                    if( document instanceof DocumentsContainer ) {
1350
                        Document subdoc = ((DocumentsContainer)document).getActiveDocument(documentClass);
1351
                        return subdoc;
1352
                    }
1353

    
1354
                } catch (ClassCastException e) {
1355
                    // Do nothing, skip this window
1356
                }
1357
            }
1358
        }
1359
        return null;
1360
    }
1361

    
1362
    @Override
1363
    public void addObserver(Observer o) {
1364
        observableHelper.addObserver(o);
1365
    }
1366

    
1367
    @Override
1368
    public void deleteObserver(Observer o) {
1369
        observableHelper.deleteObserver(o);
1370
    }
1371

    
1372
    @Override
1373
    public void deleteObservers() {
1374
        observableHelper.deleteObservers();
1375
    }
1376

    
1377
    private ProjectNotification notifyObservers(int type) {
1378
        return notifyObservers(new DefaultProjectNotification(type));
1379
    }
1380

    
1381
    private ProjectNotification notifyObservers(int type, Document document) {
1382
        return notifyObservers(new DefaultProjectNotification(type, document));
1383
    }
1384
    
1385
    private ProjectNotification notifyObservers(int type, PersistentState state) {
1386
        return notifyObservers(new DefaultProjectNotification(type, state));
1387
    }
1388

    
1389
    private ProjectNotification notifyObservers(int type, File file) {
1390
        return notifyObservers(new DefaultProjectNotification(type, file));
1391
    }
1392

    
1393
    private ProjectNotification notifyObservers(ProjectNotification notifycation) {
1394
        try {
1395
            observableHelper.notifyObservers(this, notifycation);
1396
        } catch (Exception ex) {
1397
            LOG.info("Can't notify observers", ex);
1398
        }
1399
        return notifycation;
1400
    }
1401
    
1402
    class DefaultProjectNotification implements ProjectNotification {
1403

    
1404
        private int type;
1405
        private Document document;
1406
        private File file;
1407
        private boolean processCanceled = false;
1408
        private PersistentState state = null;
1409

    
1410
        DefaultProjectNotification(int type) {
1411
            this.type = type;
1412
        }
1413

    
1414
        DefaultProjectNotification(int type, Document document) {
1415
            this(type);
1416
            this.document = document;
1417
        }
1418

    
1419
        DefaultProjectNotification(int type, File file) {
1420
            this(type);
1421
            this.file = file;
1422
        }
1423

    
1424
        DefaultProjectNotification(int type, PersistentState state) {
1425
            this(type);
1426
            this.state = state;
1427
        }
1428

    
1429
        @Override
1430
        public int getNotificationType() {
1431
            return type;
1432
        }
1433

    
1434
        @Override
1435
        public Document getDocument() {
1436
            return document;
1437
        }
1438

    
1439
        @Override
1440
        public void cancelProcess() {
1441
            processCanceled = true;
1442
        }
1443

    
1444
        @Override
1445
        public boolean isProcessCanceled() {
1446
            return processCanceled;
1447
        }
1448

    
1449
        @Override
1450
        public File getFile() {
1451
            return file;
1452
        }
1453

    
1454
        @Override
1455
        public PersistentState getState() {
1456
            return this.state;
1457
        }
1458

    
1459
        @Override
1460
        public Project getProject() {
1461
            return DefaultProject.this;
1462
        }
1463
    }
1464

    
1465
}