001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 * 
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.file;
029
030import org.opencms.db.CmsDriverManager;
031import org.opencms.db.CmsResourceState;
032import org.opencms.main.CmsIllegalArgumentException;
033import org.opencms.util.A_CmsModeIntEnumeration;
034import org.opencms.util.CmsStringUtil;
035import org.opencms.util.CmsUUID;
036
037import java.io.Serializable;
038
039/**
040 * Base class for all OpenCms VFS resources like <code>{@link CmsFile}</code> or <code>{@link CmsFolder}</code>.<p>
041 *
042 * The OpenCms VFS resource is an important object for using the OpenCms API. 
043 * Basically, all entries in the OpenCms VFS are considered to be "resources". 
044 * Currently, only two types of resources exists:<ul>
045 * <li>Files, which are represented by the subclass {@link CmsFile}.
046 * <li>Folders (also called Directories), which are represented by the subclass {@link CmsFolder}.
047 * </ul>
048 * 
049 * If you have a resource, you can use {@link #isFile()} or {@link #isFolder()} to learn what kind of 
050 * subclass you have. Please note that this is usually not required, as the only real difference between a
051 * {@link CmsFile} and a {@link CmsResource} is that the {@link CmsFile} also has the contents of the file,
052 * which you can obtain using {@link CmsFile#getContents()}. As long as you don't need the content, you can
053 * use the {@link CmsResource} for everything else. This is even more true for a {@link CmsFolder}, here you 
054 * will need the subclass only in special cases, since the signature is identical to {@link CmsResource}.<p>
055 * 
056 * A OpenCms VFS resource can have any number of properties attached, which are represented by a {@link CmsProperty}.
057 * To read the properties for a resource, use {@link CmsObject#readPropertyObject(CmsResource, String, boolean)}
058 * or use {@link CmsObject#readPropertyObjects(CmsResource, boolean)} to read all properties of the resource.<p>
059 * 
060 * @since 6.0.0 
061 */
062public class CmsResource implements I_CmsResource, Cloneable, Serializable, Comparable<I_CmsResource> {
063
064    /**
065     *  Enumeration class for resource copy modes.<p>
066     */
067    public static final class CmsResourceCopyMode extends A_CmsModeIntEnumeration {
068
069        /** Copy mode for copy resources as new resource. */
070        protected static final CmsResourceCopyMode MODE_COPY_AS_NEW = new CmsResourceCopyMode(1);
071
072        /** Copy mode for copy resources as sibling. */
073        protected static final CmsResourceCopyMode MODE_COPY_AS_SIBLING = new CmsResourceCopyMode(2);
074
075        /** Copy mode to preserve siblings during copy. */
076        protected static final CmsResourceCopyMode MODE_COPY_PRESERVE_SIBLING = new CmsResourceCopyMode(3);
077
078        /** Version id required for safe serialization. */
079        private static final long serialVersionUID = 9081630878178799137L;
080
081        /**
082         * Private constructor.<p>
083         * 
084         * @param mode the copy mode integer representation
085         */
086        private CmsResourceCopyMode(int mode) {
087
088            super(mode);
089        }
090
091        /**
092         * Returns the copy mode object from the old copy mode integer.<p>
093         * 
094         * @param mode the old copy mode integer
095         * 
096         * @return the copy mode object
097         */
098        public static CmsResourceCopyMode valueOf(int mode) {
099
100            switch (mode) {
101                case 1:
102                    return CmsResourceCopyMode.MODE_COPY_AS_NEW;
103                case 2:
104                    return CmsResourceCopyMode.MODE_COPY_AS_SIBLING;
105                case 3:
106                default:
107                    return CmsResourceCopyMode.MODE_COPY_PRESERVE_SIBLING;
108            }
109        }
110    }
111
112    /**
113     *  Enumeration class for resource delete modes.<p>
114     */
115    public static final class CmsResourceDeleteMode extends A_CmsModeIntEnumeration {
116
117        /** Signals that siblings of this resource should not be deleted. */
118        protected static final CmsResourceDeleteMode MODE_DELETE_PRESERVE_SIBLINGS = new CmsResourceDeleteMode(1);
119
120        /** Signals that siblings of this resource should be deleted. */
121        protected static final CmsResourceDeleteMode MODE_DELETE_REMOVE_SIBLINGS = new CmsResourceDeleteMode(2);
122
123        /** Version id required for safe serialization. */
124        private static final long serialVersionUID = 2010402524576925865L;
125
126        /**
127         * Private constructor.<p>
128         * 
129         * @param mode the delete mode integer representation
130         */
131        private CmsResourceDeleteMode(int mode) {
132
133            super(mode);
134        }
135
136        /**
137         * Returns the delete mode object from the old delete mode integer.<p>
138         * 
139         * @param mode the old delete mode integer
140         * 
141         * @return the delete mode object
142         */
143        public static CmsResourceDeleteMode valueOf(int mode) {
144
145            switch (mode) {
146                case 1:
147                    return CmsResourceDeleteMode.MODE_DELETE_PRESERVE_SIBLINGS;
148                case 2:
149                default:
150                    return CmsResourceDeleteMode.MODE_DELETE_REMOVE_SIBLINGS;
151            }
152        }
153    }
154
155    /**
156     *  Enumeration class for resource undo changes modes.<p>
157     */
158    public static final class CmsResourceUndoMode extends A_CmsModeIntEnumeration {
159
160        /** Indicates that the undo method will only undo content changes. */
161        public static final CmsResourceUndoMode MODE_UNDO_CONTENT = new CmsResourceUndoMode(1);
162
163        /** Indicates that the undo method will only recursive undo content changes. */
164        public static final CmsResourceUndoMode MODE_UNDO_CONTENT_RECURSIVE = new CmsResourceUndoMode(2);
165
166        /** Indicates that the undo method will undo move operations and content changes. */
167        public static final CmsResourceUndoMode MODE_UNDO_MOVE_CONTENT = new CmsResourceUndoMode(3);
168
169        /** Indicates that the undo method will undo move operations and recursive content changes. */
170        public static final CmsResourceUndoMode MODE_UNDO_MOVE_CONTENT_RECURSIVE = new CmsResourceUndoMode(4);
171
172        /** Version id required for safe serialization. */
173        private static final long serialVersionUID = 3521620626485212068L;
174
175        /**
176         * private constructor.<p>
177         * 
178         * @param mode the undo changes mode integer representation
179         */
180        private CmsResourceUndoMode(int mode) {
181
182            super(mode);
183        }
184
185        /**
186         * Returns the undo mode object from the old undo mode integer.<p>
187         * 
188         * @param mode the old undo mode integer
189         * 
190         * @return the undo mode object
191         */
192        public static CmsResourceUndoMode valueOf(int mode) {
193
194            switch (mode) {
195                case 1:
196                    return CmsResourceUndoMode.MODE_UNDO_CONTENT;
197                case 2:
198                    return CmsResourceUndoMode.MODE_UNDO_CONTENT_RECURSIVE;
199                case 3:
200                    return CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT;
201                case 4:
202                default:
203                    return CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT_RECURSIVE;
204            }
205        }
206
207        /**
208         * Returns a mode that includes the move operation with the same semantic as this mode.<p>
209         * 
210         * @return a mode that includes the move operation with the same semantic as this mode
211         */
212        public CmsResourceUndoMode includeMove() {
213
214            if (!isUndoMove()) {
215                // keep the same semantic but including move 
216                return CmsResourceUndoMode.valueOf(getMode() + 2);
217            }
218            return this;
219        }
220
221        /**
222         * Returns <code>true</code> if this undo operation is recursive.<p>
223         * 
224         * @return <code>true</code> if this undo operation is recursive
225         */
226        public boolean isRecursive() {
227
228            return getMode() > CmsResource.UNDO_CONTENT.getMode();
229        }
230
231        /**
232         * Returns <code>true</code> if this undo mode will undo move operations.<p>
233         * 
234         * @return <code>true</code> if this undo mode will undo move operations
235         */
236        public boolean isUndoMove() {
237
238            return getMode() > CmsResource.UNDO_CONTENT_RECURSIVE.getMode();
239        }
240
241        /**
242         * @see java.lang.Object#toString()
243         */
244        @Override
245        public String toString() {
246
247            return String.valueOf(getMode());
248        }
249    }
250
251    /** Copy mode for copy resources as new resource. */
252    public static final CmsResourceCopyMode COPY_AS_NEW = CmsResourceCopyMode.MODE_COPY_AS_NEW;
253
254    /** Copy mode for copy resources as sibling. */
255    public static final CmsResourceCopyMode COPY_AS_SIBLING = CmsResourceCopyMode.MODE_COPY_AS_SIBLING;
256
257    /** Copy mode to preserve siblings during copy. */
258    public static final CmsResourceCopyMode COPY_PRESERVE_SIBLING = CmsResourceCopyMode.MODE_COPY_PRESERVE_SIBLING;
259
260    /** The default expiration date of a resource, which is: never expires. */
261    public static final long DATE_EXPIRED_DEFAULT = Long.MAX_VALUE;
262
263    /** The default release date of a resource, which is: always released. */
264    public static final long DATE_RELEASED_DEFAULT = 0;
265
266    /** A special date that indicates release and expiration information are to be ignored. */
267    public static final long DATE_RELEASED_EXPIRED_IGNORE = Long.MIN_VALUE;
268
269    /** Signals that siblings of this resource should not be deleted. */
270    public static final CmsResourceDeleteMode DELETE_PRESERVE_SIBLINGS = CmsResourceDeleteMode.MODE_DELETE_PRESERVE_SIBLINGS;
271
272    /** Signals that siblings of this resource should be deleted. */
273    public static final CmsResourceDeleteMode DELETE_REMOVE_SIBLINGS = CmsResourceDeleteMode.MODE_DELETE_REMOVE_SIBLINGS;
274
275    /** Flag to indicate that this is an internal resource, that can't be accessed directly. */
276    public static final int FLAG_INTERNAL = 512;
277
278    /** The resource is linked inside a site folder specified in the OpenCms configuration. */
279    public static final int FLAG_LABELED = 2;
280
281    /** Flag to indicate that this is a temporary resource. */
282    public static final int FLAG_TEMPFILE = 1024;
283
284    /** The name constraints when generating new resources. */
285    public static final String NAME_CONSTRAINTS = "-._~$";
286
287    /** Indicates if a resource has been changed in the offline version when compared to the online version. */
288    public static final CmsResourceState STATE_CHANGED = CmsResourceState.STATE_CHANGED;
289
290    /** Indicates if a resource has been deleted in the offline version when compared to the online version. */
291    public static final CmsResourceState STATE_DELETED = CmsResourceState.STATE_DELETED;
292
293    /**
294     * Special state value that indicates the current state must be kept on a resource,
295     * this value must never be written to the database.
296     */
297    public static final CmsResourceState STATE_KEEP = CmsResourceState.STATE_KEEP;
298
299    /** Indicates if a resource is new in the offline version when compared to the online version. */
300    public static final CmsResourceState STATE_NEW = CmsResourceState.STATE_NEW;
301
302    /** Indicates if a resource is unchanged in the offline version when compared to the online version. */
303    public static final CmsResourceState STATE_UNCHANGED = CmsResourceState.STATE_UNCHANGED;
304
305    /** 
306     * Prefix for temporary files in the VFS. 
307     * 
308     * @see #isTemporaryFile()
309     * @see #isTemporaryFileName(String)  
310     */
311    public static final String TEMP_FILE_PREFIX = CmsDriverManager.TEMP_FILE_PREFIX;
312
313    /** Flag for leaving a date unchanged during a touch operation. */
314    public static final long TOUCH_DATE_UNCHANGED = -1;
315
316    /** Indicates that the undo method will only undo content changes. */
317    public static final CmsResourceUndoMode UNDO_CONTENT = CmsResourceUndoMode.MODE_UNDO_CONTENT;
318
319    /** Indicates that the undo method will only recursive undo content changes. */
320    public static final CmsResourceUndoMode UNDO_CONTENT_RECURSIVE = CmsResourceUndoMode.MODE_UNDO_CONTENT_RECURSIVE;
321
322    /** Indicates that the undo method will undo move operations and content changes. */
323    public static final CmsResourceUndoMode UNDO_MOVE_CONTENT = CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT;
324
325    /** Indicates that the undo method will undo move operations and recursive content changes. */
326    public static final CmsResourceUndoMode UNDO_MOVE_CONTENT_RECURSIVE = CmsResourceUndoMode.MODE_UNDO_MOVE_CONTENT_RECURSIVE;
327
328    /** The vfs path of the sites master folder. */
329    public static final String VFS_FOLDER_SITES = "/sites";
330
331    /** The vfs path of the system folder. */
332    public static final String VFS_FOLDER_SYSTEM = "/system";
333
334    /** Serial version UID required for safe serialization. */
335    private static final long serialVersionUID = 257325098790850498L;
336
337    /** The date of the last modification of the content of this resource. */
338    protected long m_dateContent = System.currentTimeMillis();
339
340    /** The size of the content. */
341    protected int m_length;
342
343    /** The creation date of this resource. */
344    private long m_dateCreated;
345
346    /** The expiration date of this resource. */
347    private long m_dateExpired;
348
349    /** The date of the last modification of this resource. */
350    private long m_dateLastModified;
351
352    /** The release date of this resource. */
353    private long m_dateReleased;
354
355    /** The flags of this resource. */
356    private int m_flags;
357
358    /** Indicates if this resource is a folder or not. */
359    private boolean m_isFolder;
360
361    /** Boolean flag whether the timestamp of this resource was modified by a touch command. */
362    private boolean m_isTouched;
363
364    /** The project id where this resource has been last modified in. */
365    private CmsUUID m_projectLastModified;
366
367    /** The id of the resource database record. */
368    private CmsUUID m_resourceId;
369
370    /** The name of a resource with it's full path from the root folder including the current site root. */
371    private String m_rootPath;
372
373    /** The number of links that point to this resource. */
374    private int m_siblingCount;
375
376    /** The state of this resource. */
377    private CmsResourceState m_state;
378
379    /** The id of the structure database record. */
380    private CmsUUID m_structureId;
381
382    /** The resource type id of this resource. */
383    private int m_typeId;
384
385    /** The id of the user who created this resource. */
386    private CmsUUID m_userCreated;
387
388    /** The id of the user who modified this resource last. */
389    private CmsUUID m_userLastModified;
390
391    /** The version number of this resource. */
392    private int m_version;
393
394    /**
395     * Creates a new CmsRecource object.<p>
396     * 
397     * @param structureId the id of this resources structure record
398     * @param resourceId the id of this resources resource record
399     * @param rootPath the root path to the resource
400     * @param type the type of this resource
401     * @param isFolder must be true if the resource is a folder, or false if it is a file
402     * @param flags the flags of this resource
403     * @param projectId the project id this resource was last modified in
404     * @param state the state of this resource
405     * @param dateCreated the creation date of this resource
406     * @param userCreated the id of the user who created this resource
407     * @param dateLastModified the date of the last modification of this resource
408     * @param userLastModified the id of the user who did the last modification of this resource
409     * @param dateReleased the release date of this resource
410     * @param dateExpired the expiration date of this resource
411     * @param linkCount the count of all siblings of this resource 
412     * @param size the size of the file content of this resource 
413     * @param dateContent the date of the last modification of the content of this resource 
414     * @param version the version number of this resource   
415     */
416    public CmsResource(
417        CmsUUID structureId,
418        CmsUUID resourceId,
419        String rootPath,
420        int type,
421        boolean isFolder,
422        int flags,
423        CmsUUID projectId,
424        CmsResourceState state,
425        long dateCreated,
426        CmsUUID userCreated,
427        long dateLastModified,
428        CmsUUID userLastModified,
429        long dateReleased,
430        long dateExpired,
431        int linkCount,
432        int size,
433        long dateContent,
434        int version) {
435
436        m_structureId = structureId;
437        m_resourceId = resourceId;
438        m_rootPath = rootPath;
439        m_typeId = type;
440        m_isFolder = isFolder;
441        m_flags = flags;
442        m_projectLastModified = projectId;
443        m_state = state;
444        m_dateCreated = dateCreated;
445        m_userCreated = userCreated;
446        m_dateLastModified = dateLastModified;
447        m_userLastModified = userLastModified;
448        m_dateReleased = dateReleased;
449        m_dateExpired = dateExpired;
450        m_siblingCount = linkCount;
451        m_length = size;
452        m_dateContent = dateContent;
453        m_version = version;
454        m_isTouched = false;
455    }
456
457    /**
458     * Checks if the provided resource name is a valid resource name, 
459     * that is contains only valid characters.<p>
460     * 
461     * A resource name can only be composed of digits, 
462     * standard ASCII letters and the symbols defined in {@link #NAME_CONSTRAINTS}.
463     * A resource name must also not contain only dots.<p>
464     *
465     * @param name the resource name to check
466     * 
467     * @throws CmsIllegalArgumentException if the given resource name is not valid
468     */
469    public static void checkResourceName(String name) throws CmsIllegalArgumentException {
470
471        if (CmsStringUtil.isEmptyOrWhitespaceOnly(name)) {
472            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_RESOURCENAME_EMPTY_0, name));
473        }
474
475        CmsStringUtil.checkName(name, NAME_CONSTRAINTS, Messages.ERR_BAD_RESOURCENAME_4, Messages.get());
476
477        // check for filenames that have only dots (which will cause issues in the static export)
478        boolean onlydots = true;
479        // this must be done only for the last name (not for parent folders)
480        String lastName = CmsResource.getName(name);
481        int l = lastName.length();
482        for (int i = 0; i < l; i++) {
483            char c = lastName.charAt(i);
484            if ((c != '.') && (c != '/')) {
485                onlydots = false;
486            }
487        }
488        if (onlydots) {
489            throw new CmsIllegalArgumentException(Messages.get().container(
490                Messages.ERR_BAD_RESOURCENAME_DOTS_1,
491                lastName));
492        }
493    }
494
495    /**
496     * Returns the folder path of the resource with the given name.<p>
497     * 
498     * If the resource name denotes a folder (that is ends with a "/"), the complete path of the folder 
499     * is returned (not the parent folder path).<p>
500     * 
501     * This is achieved by just cutting of everything behind the last occurrence of a "/" character
502     * in the String, no check if performed if the resource exists or not in the VFS, 
503     * only resources that end with a "/" are considered to be folders.
504     * 
505     * Example: Returns <code>/system/def/</code> for the
506     * resource <code>/system/def/file.html</code> and 
507     * <code>/system/def/</code> for the (folder) resource <code>/system/def/</code>.
508     *
509     * @param resource the name of a resource
510     * @return the folder of the given resource
511     */
512    public static String getFolderPath(String resource) {
513
514        return resource.substring(0, resource.lastIndexOf('/') + 1);
515    }
516
517    /**
518     * Returns the name of a resource without the path information.<p>
519     * 
520     * The resource name of a file is the name of the file.
521     * The resource name of a folder is the folder name with trailing "/".
522     * The resource name of the root folder is <code>/</code>.<p>
523     * 
524     * Example: <code>/system/workplace/</code> has the resource name <code>workplace/</code>.
525     * 
526     * @param resource the resource to get the name for
527     * @return the name of a resource without the path information
528     */
529    public static String getName(String resource) {
530
531        if ("/".equals(resource)) {
532            return "/";
533        }
534        // remove the last char, for a folder this will be "/", for a file it does not matter
535        String parent = (resource.substring(0, resource.length() - 1));
536        // now as the name does not end with "/", check for the last "/" which is the parent folder name
537        return resource.substring(parent.lastIndexOf('/') + 1);
538    }
539
540    /**
541     * Returns the absolute parent folder name of a resource.<p>
542     * 
543     * The parent resource of a file is the folder of the file.
544     * The parent resource of a folder is the parent folder.
545     * The parent resource of the root folder is <code>null</code>.<p>
546     * 
547     * Example: <code>/system/workplace/</code> has the parent <code>/system/</code>.
548     * 
549     * @param resource the resource to find the parent folder for
550     * @return the calculated parent absolute folder path, or <code>null</code> for the root folder 
551     */
552    public static String getParentFolder(String resource) {
553
554        if (CmsStringUtil.isEmptyOrWhitespaceOnly(resource) || "/".equals(resource)) {
555            return null;
556        }
557        // remove the last char, for a folder this will be "/", for a file it does not matter
558        String parent = (resource.substring(0, resource.length() - 1));
559        // now as the name does not end with "/", check for the last "/" which is the parent folder name
560        return parent.substring(0, parent.lastIndexOf('/') + 1);
561    }
562
563    /**
564     * Returns the directory level of a resource.<p>
565     * 
566     * The root folder "/" has level 0,
567     * a folder "/foo/" would have level 1,
568     * a folfer "/foo/bar/" level 2 etc.<p> 
569     * 
570     * @param resource the resource to determine the directory level for
571     * @return the directory level of a resource
572     */
573    public static int getPathLevel(String resource) {
574
575        int level = -1;
576        int pos = 0;
577        while (resource.indexOf('/', pos) >= 0) {
578            pos = resource.indexOf('/', pos) + 1;
579            level++;
580        }
581        return level;
582    }
583
584    /**
585     * Returns the name of a parent folder of the given resource, 
586     * that is either minus levels up 
587     * from the current folder, or that is plus levels down from the 
588     * root folder.<p>
589     * 
590     * @param resource the name of a resource
591     * @param level of levels to walk up or down
592     * @return the name of a parent folder of the given resource 
593     */
594    public static String getPathPart(String resource, int level) {
595
596        resource = getFolderPath(resource);
597        String result = null;
598        int pos = 0, count = 0;
599        if (level >= 0) {
600            // Walk down from the root folder /
601            while ((count < level) && (pos > -1)) {
602                count++;
603                pos = resource.indexOf('/', pos + 1);
604            }
605        } else {
606            // Walk up from the current folder
607            pos = resource.length();
608            while ((count > level) && (pos > -1)) {
609                count--;
610                pos = resource.lastIndexOf('/', pos - 1);
611            }
612        }
613        if (pos > -1) {
614            // To many levels walked
615            result = resource.substring(0, pos + 1);
616        } else {
617            // Add trailing slash
618            result = (level < 0) ? "/" : resource;
619        }
620        return result;
621    }
622
623    /**
624     * Returns true if the resource name certainly denotes a folder, that is ends with a "/".<p>
625     * 
626     * @param resource the resource to check
627     * @return true if the resource name certainly denotes a folder, that is ends with a "/"
628     */
629    public static boolean isFolder(String resource) {
630
631        return CmsStringUtil.isNotEmpty(resource) && (resource.charAt(resource.length() - 1) == '/');
632    }
633
634    /**
635     * Returns <code>true</code> if the given resource path points to a temporary file name.<p>
636     * 
637     * A resource name is considered a temporary file name if the name of the file 
638     * (without parent folders) starts with the prefix char <code>'~'</code> (tilde).
639     * Existing parent folder elements are removed from the path before the file name is checked.<p>
640     * 
641     * @param path the resource path to check
642     * 
643     * @return <code>true</code> if the given resource name is a temporary file name
644     * 
645     * @see #isTemporaryFile()
646     */
647    public static boolean isTemporaryFileName(String path) {
648
649        return (path != null) && getName(path).startsWith(TEMP_FILE_PREFIX);
650    }
651
652    /**
653     * Returns a clone of this Objects instance.<p>
654     * 
655     * @return a clone of this instance
656     */
657    @Override
658    public Object clone() {
659
660        return getCopy();
661    }
662
663    /**
664     * Uses the resource root path to compare two resources.<p> 
665     * 
666     * Please note a number of additional comparators for resources exists as members of this class.<p>
667     * 
668     * @see java.lang.Comparable#compareTo(java.lang.Object)
669     * 
670     * @see #COMPARE_DATE_RELEASED
671     * @see #COMPARE_ROOT_PATH
672     * @see #COMPARE_ROOT_PATH_IGNORE_CASE
673     * @see #COMPARE_ROOT_PATH_IGNORE_CASE_FOLDERS_FIRST
674     */
675    public int compareTo(I_CmsResource obj) {
676
677        if (obj == this) {
678            return 0;
679        }
680        return m_rootPath.compareTo(obj.getRootPath());
681    }
682
683    /**
684     * Two resources are considered equal in case their structure id is equal.<p>
685     * 
686     * @see java.lang.Object#equals(java.lang.Object)
687     */
688    @Override
689    public boolean equals(Object obj) {
690
691        if (obj == null) {
692            return false;
693        }
694
695        if (obj == this) {
696            return true;
697        }
698        if (obj instanceof CmsResource) {
699            return ((CmsResource)obj).m_structureId.equals(m_structureId);
700        }
701        return false;
702    }
703
704    /**
705     * Creates a copy of this resource.<p>
706     * 
707     * This is useful in case you want to create a copy of a resource and 
708     * really make sure won't get a {@link CmsFile} or {@link CmsFolder}, which may happen 
709     * if you just call {@link #clone()}.<p>
710     * 
711     * @return a copy of this resource
712     */
713    public CmsResource getCopy() {
714
715        CmsResource result = new CmsResource(
716            m_structureId,
717            m_resourceId,
718            m_rootPath,
719            m_typeId,
720            m_isFolder,
721            m_flags,
722            m_projectLastModified,
723            m_state,
724            m_dateCreated,
725            m_userCreated,
726            m_dateLastModified,
727            m_userLastModified,
728            m_dateReleased,
729            m_dateExpired,
730            m_siblingCount,
731            m_length,
732            m_dateContent,
733            m_version);
734
735        if (isTouched()) {
736            result.setDateLastModified(m_dateLastModified);
737        }
738
739        return result;
740    }
741
742    /**
743     * Returns the date of the last modification of the content of this resource.<p>
744     *
745     * This applies only to resources of type {@link CmsFile}, since a {@link CmsFolder} has no content.
746     * In case of a folder, <code>-1</code> is always returned as content date.<p>
747     *
748     * Any modification of a resource, including changes to the resource properties, 
749     * will increase the "date of last modification" which is returned by {@link #getDateLastModified()}.
750     * The "date of the content" as returned by this method only changes when the
751     * file content as returned by {@link CmsFile#getContents()} is changed.<p>
752     *
753     * @return the date of the last modification of the content of this resource
754     * 
755     * @since 7.0.0
756     */
757    public long getDateContent() {
758
759        return m_dateContent;
760    }
761
762    /**
763     * Returns the date of the creation of this resource.<p>
764     *
765     * @return the date of the creation of this resource
766     */
767    public long getDateCreated() {
768
769        return m_dateCreated;
770    }
771
772    /**
773     * Returns the expiration date this resource.<p>
774     *
775     * If the expiration date has not been set, {@link #DATE_EXPIRED_DEFAULT} is returned. 
776     * This means: The resource does never expire.<p>
777     *
778     * @return the expiration date of this resource
779     */
780    public long getDateExpired() {
781
782        return m_dateExpired;
783    }
784
785    /**
786     * Returns the date of the last modification of this resource.<p>
787     *
788     * @return the date of the last modification of this resource
789     */
790    public long getDateLastModified() {
791
792        return m_dateLastModified;
793    }
794
795    /**
796     * Returns the release date this resource.<p>
797     *
798     * If the release date has not been set, {@link #DATE_RELEASED_DEFAULT} is returned. 
799     * This means: The resource has always been released.<p>
800     *
801     * @return the release date of this resource
802     */
803    public long getDateReleased() {
804
805        return m_dateReleased;
806    }
807
808    /**
809     * Returns the flags of this resource.<p>
810     *
811     * @return the flags of this resource
812     * 
813     * @see #setFlags(int) for an explanation of the resource flags
814     */
815    public int getFlags() {
816
817        return m_flags;
818    }
819
820    /**
821     * Returns the content length of this resource.<p>
822     *
823     * If the resource is a file, then this is the byte size of the file content.
824     * If the resource is a folder, then the size is always -1.<p>
825     *
826     * @return the content length of the content
827     */
828    public int getLength() {
829
830        // make sure folders always have a -1 size
831        return m_isFolder ? -1 : m_length;
832    }
833
834    /**
835     * Returns the file name of this resource without parent folders, for example <code>index.html</code>.<p>
836     *
837     * @return the file name of this resource without parent folders
838     */
839    public String getName() {
840
841        String name = getName(m_rootPath);
842        if (name.charAt(name.length() - 1) == '/') {
843            return name.substring(0, name.length() - 1);
844        } else {
845            return name;
846        }
847    }
848
849    /**
850     * Returns the id of the {@link CmsProject} where this resource has been last modified.<p>
851     *
852     * @return the id of the {@link CmsProject} where this resource has been last modified, or <code>null</code>
853     */
854    public CmsUUID getProjectLastModified() {
855
856        return m_projectLastModified;
857    }
858
859    /**
860     * Returns the id of the database content record of this resource.<p>
861     *
862     * @return the id of the database content record of this resource
863     */
864    public CmsUUID getResourceId() {
865
866        return m_resourceId;
867    }
868
869    /**
870     * Returns the name of this resource with it's full path from the top level root folder, 
871     * for example <code>/sites/default/myfolder/index.html</code>.<p>
872     *
873     * In a presentation level application usually the current site root must be
874     * cut of from the root path. Use {@link CmsObject#getSitePath(CmsResource)} 
875     * to get the "absolute" path of a resource in the current site.<p>
876     *
877     * @return the name of this resource with it's full path from the top level root folder
878     * 
879     * @see CmsObject#getSitePath(CmsResource)
880     * @see CmsRequestContext#getSitePath(CmsResource)
881     * @see CmsRequestContext#removeSiteRoot(String) 
882     */
883    public String getRootPath() {
884
885        return m_rootPath;
886    }
887
888    /**
889     * Returns the number of siblings of this resource, also counting this resource.<p>
890     * 
891     * If a resource has no sibling, the total sibling count for this resource is <code>1</code>, 
892     * if a resource has <code>n</code> siblings, the sibling count is <code>n + 1</code>.<p> 
893     * 
894     * @return the number of siblings of this resource, also counting this resource
895     */
896    public int getSiblingCount() {
897
898        return m_siblingCount;
899    }
900
901    /**
902     * Returns the state of this resource.<p>
903     *
904     * This may be {@link CmsResource#STATE_UNCHANGED}, 
905     * {@link CmsResource#STATE_CHANGED}, {@link CmsResource#STATE_NEW} 
906     * or {@link CmsResource#STATE_DELETED}.<p>
907     *
908     * @return the state of this resource
909     */
910    public CmsResourceState getState() {
911
912        return m_state;
913    }
914
915    /**
916     * Returns the id of the database structure record of this resource.<p>
917     * 
918     * @return the id of the database structure record of this resource
919     */
920    public CmsUUID getStructureId() {
921
922        return m_structureId;
923    }
924
925    /**
926     * Returns the resource type id for this resource.<p>
927     *
928     * @return the resource type id of this resource
929     */
930    public int getTypeId() {
931
932        return m_typeId;
933    }
934
935    /**
936     * Returns the user id of the {@link CmsUser} who created this resource.<p>
937     * 
938     * @return the user id of the {@link CmsUser} who created this resource
939     */
940    public CmsUUID getUserCreated() {
941
942        return m_userCreated;
943    }
944
945    /**
946     * Returns the id of the {@link CmsUser} who made the last modification on this resource.<p>
947     *
948     * @return the id of the {@link CmsUser} who made the last modification on this resource
949     */
950    public CmsUUID getUserLastModified() {
951
952        return m_userLastModified;
953    }
954
955    /**
956     * Returns the current version number of this resource.<p>
957     *
958     * @return the current version number of this resource
959     */
960    public int getVersion() {
961
962        return m_version;
963    }
964
965    /**
966     * @see java.lang.Object#hashCode()
967     */
968    @Override
969    public int hashCode() {
970
971        if (m_structureId != null) {
972            return m_structureId.hashCode();
973        }
974
975        return CmsUUID.getNullUUID().hashCode();
976    }
977
978    /** 
979     * Returns <code>true</code> if this resource is expired at the given time according to the 
980     * information stored in {@link #getDateExpired()}.<p>
981     * 
982     * @param time the time to check the expiration date against
983     * 
984     * @return <code>true</code> if this resource is expired at the given time
985     *      
986     * @see #isReleased(long)
987     * @see #isReleasedAndNotExpired(long)
988     * @see #DATE_RELEASED_EXPIRED_IGNORE
989     * @see CmsResource#getDateReleased()
990     * @see CmsRequestContext#getRequestTime()
991     */
992    public boolean isExpired(long time) {
993
994        return (time > m_dateExpired) && (time != DATE_RELEASED_EXPIRED_IGNORE);
995    }
996
997    /**
998     * Returns <code>true</code> if the resource is a {@link CmsFile}, that is not a {@link CmsFolder}.<p>
999     *
1000     * @return true if this resource is a file, false otherwise
1001     */
1002    public boolean isFile() {
1003
1004        return !m_isFolder;
1005    }
1006
1007    /**
1008     * Returns <code>true</code> if the resource is a {@link CmsFolder}, that is not a {@link CmsFile}.<p>
1009     *
1010     * @return true if this resource is a folder, false otherwise
1011     */
1012    public boolean isFolder() {
1013
1014        return m_isFolder;
1015    }
1016
1017    /**
1018     * Returns <code>true</code> if the resource is marked as internal.<p>
1019     * 
1020     * An internal resource can be read by the OpenCms API, but it can not be delivered
1021     * by a direct request from an outside user.<p>
1022     * 
1023     * For example if the resource <code>/internal.xml</code>
1024     * has been set as marked as internal, this resource can not be requested by an HTTP request,
1025     * so when a user enters <code>http:/www.myserver.com/opencms/opencms/internal.xml</code> in the browser
1026     * this will generate a {@link CmsVfsResourceNotFoundException}.<p>
1027     * 
1028     * This state is stored as bit 1 in the resource flags.<p>
1029     * 
1030     * @return <code>true</code> if the resource is internal
1031     */
1032    public boolean isInternal() {
1033
1034        return ((m_flags & FLAG_INTERNAL) > 0);
1035    }
1036
1037    /**
1038     * Returns <code>true</code> if the resource has to be labeled with a special icon in the explorer view.<p>
1039     *
1040     * This state is stored as bit 2 in the resource flags.<p>
1041     * 
1042     * @return <code>true</code> if the resource has to be labeled in the explorer view
1043     */
1044    public boolean isLabeled() {
1045
1046        return ((m_flags & CmsResource.FLAG_LABELED) > 0);
1047    }
1048
1049    /** 
1050     * Returns <code>true</code> if this resource is released at the given time according to the 
1051     * information stored in {@link #getDateReleased()}.<p>
1052     * 
1053     * @param time the time to check the release date against
1054     * 
1055     * @return <code>true</code> if this resource is released at the given time
1056     *      
1057     * @see #isExpired(long)
1058     * @see #isReleasedAndNotExpired(long)
1059     * @see #DATE_RELEASED_EXPIRED_IGNORE
1060     * @see CmsResource#getDateReleased()
1061     * @see CmsRequestContext#getRequestTime()
1062     */
1063    public boolean isReleased(long time) {
1064
1065        return (time > m_dateReleased) || (time == DATE_RELEASED_EXPIRED_IGNORE);
1066    }
1067
1068    /** 
1069     * Returns <code>true</code> if this resource is valid at the given time according to the 
1070     * information stored in {@link #getDateReleased()} and {@link #getDateExpired()}.<p>
1071     * 
1072     * A resource is valid if it is released and not yet expired.<p>
1073     * 
1074     * @param time the time to check the release and expiration date against
1075     * 
1076     * @return <code>true</code> if this resource is valid at the given time
1077     *      
1078     * @see #isExpired(long)
1079     * @see #isReleased(long)
1080     * @see #DATE_RELEASED_EXPIRED_IGNORE
1081     * @see CmsResource#getDateReleased()
1082     * @see CmsRequestContext#getRequestTime()
1083     */
1084    public boolean isReleasedAndNotExpired(long time) {
1085
1086        return ((time < m_dateExpired) && (time > m_dateReleased)) || (time == DATE_RELEASED_EXPIRED_IGNORE);
1087    }
1088
1089    /**
1090     * Returns <code>true</code> if this resource is a temporary file.<p>
1091     * 
1092     * A resource is considered a temporary file it is a file where the
1093     * {@link CmsResource#FLAG_TEMPFILE} flag has been set, or if the file name (without parent folders)
1094     * starts with the prefix char <code>'~'</code> (tilde).<p>
1095     * 
1096     * @return <code>true</code> if the given resource name is a temporary file
1097     * 
1098     * @see #isTemporaryFileName(String)
1099     */
1100    public boolean isTemporaryFile() {
1101
1102        return isFile() && (((getFlags() & CmsResource.FLAG_TEMPFILE) > 0) || isTemporaryFileName(getName()));
1103    }
1104
1105    /**
1106     * Returns <code>true</code> if this resource was touched.<p>
1107     * 
1108     * @return <code>true</code> if this resource was touched
1109     */
1110    public boolean isTouched() {
1111
1112        return m_isTouched;
1113    }
1114
1115    /**
1116     * Sets the expiration date this resource.<p>
1117     * 
1118     * @param time the expiration date to set
1119     */
1120    public void setDateExpired(long time) {
1121
1122        m_dateExpired = time;
1123    }
1124
1125    /**
1126     * Sets the date of the last modification of this resource.<p>
1127     * 
1128     * @param time the last modification date to set
1129     */
1130    public void setDateLastModified(long time) {
1131
1132        m_isTouched = true;
1133        m_dateLastModified = time;
1134    }
1135
1136    /**
1137     * Sets the release date this resource.<p>
1138     * 
1139     * @param time the release date to set
1140     */
1141    public void setDateReleased(long time) {
1142
1143        m_dateReleased = time;
1144    }
1145
1146    /**
1147     * Sets the flags of this resource.<p>
1148     *
1149     * The resource flags integer is used as bit set that contains special information about the resource.
1150     * The following methods internally use the resource flags:<ul>
1151     * <li>{@link #isInternal()}
1152     * <li>{@link #isLabeled()}
1153     * </ul>
1154     *
1155     * @param flags the flags value to set
1156     */
1157    public void setFlags(int flags) {
1158
1159        m_flags = flags;
1160    }
1161
1162    /**
1163     * Sets the state of this resource.<p>
1164     *
1165     * @param state the state to set
1166     */
1167    public void setState(CmsResourceState state) {
1168
1169        m_state = state;
1170    }
1171
1172    /**
1173     * Sets the type of this resource.<p>
1174     *
1175     * @param type the type to set
1176     */
1177    public void setType(int type) {
1178
1179        m_typeId = type;
1180    }
1181
1182    /**
1183     * Sets the user id of the user who changed this resource.<p>
1184     *
1185     * @param resourceLastModifiedByUserId the user id of the user who changed the resource
1186     */
1187    public void setUserLastModified(CmsUUID resourceLastModifiedByUserId) {
1188
1189        m_userLastModified = resourceLastModifiedByUserId;
1190    }
1191
1192    /**
1193     * @see java.lang.Object#toString()
1194     */
1195    @Override
1196    public String toString() {
1197
1198        StringBuffer result = new StringBuffer();
1199
1200        result.append("[");
1201        result.append(this.getClass().getName());
1202        result.append(", path: ");
1203        result.append(m_rootPath);
1204        result.append(", structure id ");
1205        result.append(m_structureId);
1206        result.append(", resource id: ");
1207        result.append(m_resourceId);
1208        result.append(", type id: ");
1209        result.append(m_typeId);
1210        result.append(", folder: ");
1211        result.append(m_isFolder);
1212        result.append(", flags: ");
1213        result.append(m_flags);
1214        result.append(", project: ");
1215        result.append(m_projectLastModified);
1216        result.append(", state: ");
1217        result.append(m_state);
1218        result.append(", date created: ");
1219        result.append(new java.util.Date(m_dateCreated));
1220        result.append(", user created: ");
1221        result.append(m_userCreated);
1222        result.append(", date lastmodified: ");
1223        result.append(new java.util.Date(m_dateLastModified));
1224        result.append(", user lastmodified: ");
1225        result.append(m_userLastModified);
1226        result.append(", date released: ");
1227        result.append(new java.util.Date(m_dateReleased));
1228        result.append(", date expired: ");
1229        result.append(new java.util.Date(m_dateExpired));
1230        result.append(", date content: ");
1231        result.append(new java.util.Date(m_dateContent));
1232        result.append(", size: ");
1233        result.append(m_length);
1234        result.append(", sibling count: ");
1235        result.append(m_siblingCount);
1236        result.append(", version: ");
1237        result.append(m_version);
1238        result.append("]");
1239
1240        return result.toString();
1241    }
1242}