001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.archivers.cpio;
020
021import java.io.File;
022import java.util.Date;
023
024import org.apache.commons.compress.archivers.ArchiveEntry;
025
026/**
027 * A cpio archive consists of a sequence of files. There are several types of
028 * headers defided in two categories of new and old format. The headers are
029 * recognized by magic numbers:
030 *
031 * <ul>
032 * <li>"070701" ASCII for new portable format</li>
033 * <li>"070702" ASCII for new portable format with CRC</li>
034 * <li>"070707" ASCII for old ascii (also known as Portable ASCII, odc or old
035 * character format</li>
036 * <li>070707 binary for old binary</li>
037 * </ul>
038 *
039 * <p>The old binary format is limited to 16 bits for user id, group
040 * id, device, and inode numbers. It is limited to 4 gigabyte file
041 * sizes.
042 *
043 * The old ASCII format is limited to 18 bits for the user id, group
044 * id, device, and inode numbers. It is limited to 8 gigabyte file
045 * sizes.
046 *
047 * The new ASCII format is limited to 4 gigabyte file sizes.
048 *
049 * CPIO 2.5 knows also about tar, but it is not recognized here.</p>
050 *
051 *
052 * <h3>OLD FORMAT</h3>
053 *
054 * <p>Each file has a 76 (ascii) / 26 (binary) byte header, a variable
055 * length, NUL terminated filename, and variable length file data. A
056 * header for a filename "TRAILER!!!" indicates the end of the
057 * archive.</p>
058 *
059 * <p>All the fields in the header are ISO 646 (approximately ASCII)
060 * strings of octal numbers, left padded, not NUL terminated.</p>
061 *
062 * <pre>
063 * FIELDNAME        NOTES
064 * c_magic          The integer value octal 070707.  This value can be used to deter-
065 *                  mine whether this archive is written with little-endian or big-
066 *                  endian integers.
067 * c_dev            Device that contains a directory entry for this file
068 * c_ino            I-node number that identifies the input file to the file system
069 * c_mode           The mode specifies both the regular permissions and the file type.
070 * c_uid            Numeric User ID of the owner of the input file
071 * c_gid            Numeric Group ID of the owner of the input file
072 * c_nlink          Number of links that are connected to the input file
073 * c_rdev           For block special and character special entries, this field
074 *                  contains the associated device number.  For all other entry types,
075 *                  it should be set to zero by writers and ignored by readers.
076 * c_mtime[2]       Modification time of the file, indicated as the number of seconds
077 *                  since the start of the epoch, 00:00:00 UTC January 1, 1970.  The
078 *                  four-byte integer is stored with the most-significant 16 bits
079 *                  first followed by the least-significant 16 bits.  Each of the two
080 *                  16 bit values are stored in machine-native byte order.
081 * c_namesize       Length of the path name, including the terminating null byte
082 * c_filesize[2]    Length of the file in bytes. This is the length of the data
083 *                  section that follows the header structure. Must be 0 for
084 *                  FIFOs and directories
085 *
086 * All fields are unsigned short fields with 16-bit integer values
087 * apart from c_mtime and c_filesize which are 32-bit integer values
088 * </pre>
089 *
090 * <p>If necessary, the filename and file data are padded with a NUL byte to an even length</p>
091 *
092 * <p>Special files, directories, and the trailer are recorded with
093 * the h_filesize field equal to 0.</p>
094 *
095 * <p>In the ASCII version of this format, the 16-bit entries are represented as 6-byte octal numbers,
096 * and the 32-bit entries are represented as 11-byte octal numbers. No padding is added.</p>
097 *
098 * <h3>NEW FORMAT</h3>
099 *
100 * <p>Each file has a 110 byte header, a variable length, NUL
101 * terminated filename, and variable length file data. A header for a
102 * filename "TRAILER!!!" indicates the end of the archive. All the
103 * fields in the header are ISO 646 (approximately ASCII) strings of
104 * hexadecimal numbers, left padded, not NUL terminated.</p>
105 *
106 * <pre>
107 * FIELDNAME        NOTES
108 * c_magic[6]       The string 070701 for new ASCII, the string 070702 for new ASCII with CRC
109 * c_ino[8]
110 * c_mode[8]
111 * c_uid[8]
112 * c_gid[8]
113 * c_nlink[8]
114 * c_mtim[8]
115 * c_filesize[8]    must be 0 for FIFOs and directories
116 * c_maj[8]
117 * c_min[8]
118 * c_rmaj[8]        only valid for chr and blk special files
119 * c_rmin[8]        only valid for chr and blk special files
120 * c_namesize[8]    count includes terminating NUL in pathname
121 * c_check[8]       0 for "new" portable format; for CRC format
122 *                  the sum of all the bytes in the file
123 * </pre>
124 *
125 * <p>New ASCII Format The "new" ASCII format uses 8-byte hexadecimal
126 * fields for all numbers and separates device numbers into separate
127 * fields for major and minor numbers.</p>
128 *
129 * <p>The pathname is followed by NUL bytes so that the total size of
130 * the fixed header plus pathname is a multiple of four. Likewise, the
131 * file data is padded to a multiple of four bytes.</p>
132 *
133 * <p>This class uses mutable fields and is not considered to be
134 * threadsafe.</p>
135 *
136 * <p>Based on code from the jRPM project (http://jrpm.sourceforge.net).</p>
137 *
138 * <p>The MAGIC numbers and other constants are defined in {@link CpioConstants}</p>
139 *
140 * <p>
141 * N.B. does not handle the cpio "tar" format
142 * </p>
143 * @NotThreadSafe
144 * @see <a href="http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt">http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt</a>
145 */
146public class CpioArchiveEntry implements CpioConstants, ArchiveEntry {
147
148    // Header description fields - should be same throughout an archive
149
150    /**
151     * See constructor documenation for possible values.
152     */
153    private final short fileFormat;
154
155    /** The number of bytes in each header record; depends on the file format */
156    private final int headerSize;
157
158    /** The boundary to which the header and data elements are aligned: 0, 2 or 4 bytes */
159    private final int alignmentBoundary;
160
161    // Header fields
162
163    private long chksum = 0;
164
165    /** Number of bytes in the file */
166    private long filesize = 0;
167
168    private long gid = 0;
169
170    private long inode = 0;
171
172    private long maj = 0;
173
174    private long min = 0;
175
176    private long mode = 0;
177
178    private long mtime = 0;
179
180    private String name;
181
182    private long nlink = 0;
183
184    private long rmaj = 0;
185
186    private long rmin = 0;
187
188    private long uid = 0;
189
190    /**
191     * Creates a CpioArchiveEntry with a specified format.
192     *
193     * @param format
194     *            The cpio format for this entry.
195     * <p>
196     * Possible format values are:
197     * <pre>
198     * CpioConstants.FORMAT_NEW
199     * CpioConstants.FORMAT_NEW_CRC
200     * CpioConstants.FORMAT_OLD_BINARY
201     * CpioConstants.FORMAT_OLD_ASCII
202     * </pre>
203     */
204    public CpioArchiveEntry(final short format) {
205        switch (format) {
206        case FORMAT_NEW:
207            this.headerSize = 110;
208            this.alignmentBoundary = 4;
209            break;
210        case FORMAT_NEW_CRC:
211            this.headerSize = 110;
212            this.alignmentBoundary = 4;
213            break;
214        case FORMAT_OLD_ASCII:
215            this.headerSize = 76;
216            this.alignmentBoundary = 0;
217            break;
218        case FORMAT_OLD_BINARY:
219            this.headerSize = 26;
220            this.alignmentBoundary = 2;
221            break;
222        default:
223            throw new IllegalArgumentException("Unknown header type");
224        }
225        this.fileFormat = format;
226    }
227
228    /**
229     * Creates a CpioArchiveEntry with a specified name. The format of
230     * this entry will be the new format.
231     *
232     * @param name
233     *            The name of this entry.
234     */
235    public CpioArchiveEntry(final String name) {
236        this(FORMAT_NEW, name);
237    }
238
239    /**
240     * Creates a CpioArchiveEntry with a specified name.
241     *
242     * @param format
243     *            The cpio format for this entry.
244     * @param name
245     *            The name of this entry.
246     * <p>
247     * Possible format values are:
248     * <pre>
249     * CpioConstants.FORMAT_NEW
250     * CpioConstants.FORMAT_NEW_CRC
251     * CpioConstants.FORMAT_OLD_BINARY
252     * CpioConstants.FORMAT_OLD_ASCII
253     * </pre>
254     *
255     * @since 1.1
256     */
257    public CpioArchiveEntry(final short format, final String name) {
258        this(format);
259        this.name = name;
260    }
261
262    /**
263     * Creates a CpioArchiveEntry with a specified name. The format of
264     * this entry will be the new format.
265     *
266     * @param name
267     *            The name of this entry.
268     * @param size
269     *            The size of this entry
270     */
271    public CpioArchiveEntry(final String name, final long size) {
272        this(name);
273        this.setSize(size);
274    }
275
276    /**
277     * Creates a CpioArchiveEntry with a specified name.
278     *
279     * @param format
280     *            The cpio format for this entry.
281     * @param name
282     *            The name of this entry.
283     * @param size
284     *            The size of this entry
285     * <p>
286     * Possible format values are:
287     * <pre>
288     * CpioConstants.FORMAT_NEW
289     * CpioConstants.FORMAT_NEW_CRC
290     * CpioConstants.FORMAT_OLD_BINARY
291     * CpioConstants.FORMAT_OLD_ASCII
292     * </pre>
293     *
294     * @since 1.1
295     */
296    public CpioArchiveEntry(final short format, final String name,
297                            final long size) {
298        this(format, name);
299        this.setSize(size);
300    }
301
302    /**
303     * Creates a CpioArchiveEntry with a specified name for a
304     * specified file. The format of this entry will be the new
305     * format.
306     *
307     * @param inputFile
308     *            The file to gather information from.
309     * @param entryName
310     *            The name of this entry.
311     */
312    public CpioArchiveEntry(final File inputFile, final String entryName) {
313        this(FORMAT_NEW, inputFile, entryName);
314    }
315
316    /**
317     * Creates a CpioArchiveEntry with a specified name for a
318     * specified file.
319     *
320     * @param format
321     *            The cpio format for this entry.
322     * @param inputFile
323     *            The file to gather information from.
324     * @param entryName
325     *            The name of this entry.
326     * <p>
327     * Possible format values are:
328     * <pre>
329     * CpioConstants.FORMAT_NEW
330     * CpioConstants.FORMAT_NEW_CRC
331     * CpioConstants.FORMAT_OLD_BINARY
332     * CpioConstants.FORMAT_OLD_ASCII
333     * </pre>
334     *
335     * @since 1.1
336     */
337    public CpioArchiveEntry(final short format, final File inputFile,
338                            final String entryName) {
339        this(format, entryName, inputFile.isFile() ? inputFile.length() : 0);
340        if (inputFile.isDirectory()){
341            setMode(C_ISDIR);
342        } else if (inputFile.isFile()){
343            setMode(C_ISREG);
344        } else {
345            throw new IllegalArgumentException("Cannot determine type of file "
346                                               + inputFile.getName());
347        }
348        // TODO set other fields as needed
349        setTime(inputFile.lastModified() / 1000);
350    }
351
352    /**
353     * Check if the method is allowed for the defined format.
354     */
355    private void checkNewFormat() {
356        if ((this.fileFormat & FORMAT_NEW_MASK) == 0) {
357            throw new UnsupportedOperationException();
358        }
359    }
360
361    /**
362     * Check if the method is allowed for the defined format.
363     */
364    private void checkOldFormat() {
365        if ((this.fileFormat & FORMAT_OLD_MASK) == 0) {
366            throw new UnsupportedOperationException();
367        }
368    }
369
370    /**
371     * Get the checksum.
372     * Only supported for the new formats.
373     *
374     * @return Returns the checksum.
375     * @throws UnsupportedOperationException if the format is not a new format
376     */
377    public long getChksum() {
378        checkNewFormat();
379        return this.chksum & 0xFFFFFFFFL;
380    }
381
382    /**
383     * Get the device id.
384     *
385     * @return Returns the device id.
386     * @throws UnsupportedOperationException
387     *             if this method is called for a CpioArchiveEntry with a new
388     *             format.
389     */
390    public long getDevice() {
391        checkOldFormat();
392        return this.min;
393    }
394
395    /**
396     * Get the major device id.
397     *
398     * @return Returns the major device id.
399     * @throws UnsupportedOperationException
400     *             if this method is called for a CpioArchiveEntry with an old
401     *             format.
402     */
403    public long getDeviceMaj() {
404        checkNewFormat();
405        return this.maj;
406    }
407
408    /**
409     * Get the minor device id
410     *
411     * @return Returns the minor device id.
412     * @throws UnsupportedOperationException if format is not a new format
413     */
414    public long getDeviceMin() {
415        checkNewFormat();
416        return this.min;
417    }
418
419    /**
420     * Get the filesize.
421     *
422     * @return Returns the filesize.
423     * @see org.apache.commons.compress.archivers.ArchiveEntry#getSize()
424     */
425    @Override
426    public long getSize() {
427        return this.filesize;
428    }
429
430    /**
431     * Get the format for this entry.
432     *
433     * @return Returns the format.
434     */
435    public short getFormat() {
436        return this.fileFormat;
437    }
438
439    /**
440     * Get the group id.
441     *
442     * @return Returns the group id.
443     */
444    public long getGID() {
445        return this.gid;
446    }
447
448    /**
449     * Get the header size for this CPIO format
450     *
451     * @return Returns the header size in bytes.
452     */
453    public int getHeaderSize() {
454        return this.headerSize;
455    }
456
457    /**
458     * Get the alignment boundary for this CPIO format
459     *
460     * @return Returns the aligment boundary (0, 2, 4) in bytes
461     */
462    public int getAlignmentBoundary() {
463        return this.alignmentBoundary;
464    }
465
466    /**
467     * Get the number of bytes needed to pad the header to the alignment boundary.
468     *
469     * @return the number of bytes needed to pad the header (0,1,2,3)
470     */
471    public int getHeaderPadCount(){
472        if (this.alignmentBoundary == 0) { return 0; }
473        int size = this.headerSize + 1;  // Name has terminating null
474        if (name != null) {
475            size += name.length();
476        }
477        final int remain = size % this.alignmentBoundary;
478        if (remain > 0){
479            return this.alignmentBoundary - remain;
480        }
481        return 0;
482    }
483
484    /**
485     * Get the number of bytes needed to pad the data to the alignment boundary.
486     *
487     * @return the number of bytes needed to pad the data (0,1,2,3)
488     */
489    public int getDataPadCount(){
490        if (this.alignmentBoundary == 0) { return 0; }
491        final long size = this.filesize;
492        final int remain = (int) (size % this.alignmentBoundary);
493        if (remain > 0){
494            return this.alignmentBoundary - remain;
495        }
496        return 0;
497    }
498
499    /**
500     * Set the inode.
501     *
502     * @return Returns the inode.
503     */
504    public long getInode() {
505        return this.inode;
506    }
507
508    /**
509     * Get the mode of this entry (e.g. directory, regular file).
510     *
511     * @return Returns the mode.
512     */
513    public long getMode() {
514        return mode == 0 && !CPIO_TRAILER.equals(name) ? C_ISREG : mode;
515    }
516
517    /**
518     * Get the name.
519     *
520     * @return Returns the name.
521     */
522    @Override
523    public String getName() {
524        return this.name;
525    }
526
527    /**
528     * Get the number of links.
529     *
530     * @return Returns the number of links.
531     */
532    public long getNumberOfLinks() {
533        return nlink == 0 ?
534            isDirectory() ? 2 : 1
535            : nlink;
536    }
537
538    /**
539     * Get the remote device id.
540     *
541     * @return Returns the remote device id.
542     * @throws UnsupportedOperationException
543     *             if this method is called for a CpioArchiveEntry with a new
544     *             format.
545     */
546    public long getRemoteDevice() {
547        checkOldFormat();
548        return this.rmin;
549    }
550
551    /**
552     * Get the remote major device id.
553     *
554     * @return Returns the remote major device id.
555     * @throws UnsupportedOperationException
556     *             if this method is called for a CpioArchiveEntry with an old
557     *             format.
558     */
559    public long getRemoteDeviceMaj() {
560        checkNewFormat();
561        return this.rmaj;
562    }
563
564    /**
565     * Get the remote minor device id.
566     *
567     * @return Returns the remote minor device id.
568     * @throws UnsupportedOperationException
569     *             if this method is called for a CpioArchiveEntry with an old
570     *             format.
571     */
572    public long getRemoteDeviceMin() {
573        checkNewFormat();
574        return this.rmin;
575    }
576
577    /**
578     * Get the time in seconds.
579     *
580     * @return Returns the time.
581     */
582    public long getTime() {
583        return this.mtime;
584    }
585
586    @Override
587    public Date getLastModifiedDate() {
588        return new Date(1000 * getTime());
589    }
590
591    /**
592     * Get the user id.
593     *
594     * @return Returns the user id.
595     */
596    public long getUID() {
597        return this.uid;
598    }
599
600    /**
601     * Check if this entry represents a block device.
602     *
603     * @return TRUE if this entry is a block device.
604     */
605    public boolean isBlockDevice() {
606        return CpioUtil.fileType(mode) == C_ISBLK;
607    }
608
609    /**
610     * Check if this entry represents a character device.
611     *
612     * @return TRUE if this entry is a character device.
613     */
614    public boolean isCharacterDevice() {
615        return CpioUtil.fileType(mode) == C_ISCHR;
616    }
617
618    /**
619     * Check if this entry represents a directory.
620     *
621     * @return TRUE if this entry is a directory.
622     */
623    @Override
624    public boolean isDirectory() {
625        return CpioUtil.fileType(mode) == C_ISDIR;
626    }
627
628    /**
629     * Check if this entry represents a network device.
630     *
631     * @return TRUE if this entry is a network device.
632     */
633    public boolean isNetwork() {
634        return CpioUtil.fileType(mode) == C_ISNWK;
635    }
636
637    /**
638     * Check if this entry represents a pipe.
639     *
640     * @return TRUE if this entry is a pipe.
641     */
642    public boolean isPipe() {
643        return CpioUtil.fileType(mode) == C_ISFIFO;
644    }
645
646    /**
647     * Check if this entry represents a regular file.
648     *
649     * @return TRUE if this entry is a regular file.
650     */
651    public boolean isRegularFile() {
652        return CpioUtil.fileType(mode) == C_ISREG;
653    }
654
655    /**
656     * Check if this entry represents a socket.
657     *
658     * @return TRUE if this entry is a socket.
659     */
660    public boolean isSocket() {
661        return CpioUtil.fileType(mode) == C_ISSOCK;
662    }
663
664    /**
665     * Check if this entry represents a symbolic link.
666     *
667     * @return TRUE if this entry is a symbolic link.
668     */
669    public boolean isSymbolicLink() {
670        return CpioUtil.fileType(mode) == C_ISLNK;
671    }
672
673    /**
674     * Set the checksum. The checksum is calculated by adding all bytes of a
675     * file to transfer (crc += buf[pos] &amp; 0xFF).
676     *
677     * @param chksum
678     *            The checksum to set.
679     */
680    public void setChksum(final long chksum) {
681        checkNewFormat();
682        this.chksum = chksum & 0xFFFFFFFFL;
683    }
684
685    /**
686     * Set the device id.
687     *
688     * @param device
689     *            The device id to set.
690     * @throws UnsupportedOperationException
691     *             if this method is called for a CpioArchiveEntry with a new
692     *             format.
693     */
694    public void setDevice(final long device) {
695        checkOldFormat();
696        this.min = device;
697    }
698
699    /**
700     * Set major device id.
701     *
702     * @param maj
703     *            The major device id to set.
704     */
705    public void setDeviceMaj(final long maj) {
706        checkNewFormat();
707        this.maj = maj;
708    }
709
710    /**
711     * Set the minor device id
712     *
713     * @param min
714     *            The minor device id to set.
715     */
716    public void setDeviceMin(final long min) {
717        checkNewFormat();
718        this.min = min;
719    }
720
721    /**
722     * Set the filesize.
723     *
724     * @param size
725     *            The filesize to set.
726     */
727    public void setSize(final long size) {
728        if (size < 0 || size > 0xFFFFFFFFL) {
729            throw new IllegalArgumentException("invalid entry size <" + size
730                                               + ">");
731        }
732        this.filesize = size;
733    }
734
735    /**
736     * Set the group id.
737     *
738     * @param gid
739     *            The group id to set.
740     */
741    public void setGID(final long gid) {
742        this.gid = gid;
743    }
744
745    /**
746     * Set the inode.
747     *
748     * @param inode
749     *            The inode to set.
750     */
751    public void setInode(final long inode) {
752        this.inode = inode;
753    }
754
755    /**
756     * Set the mode of this entry (e.g. directory, regular file).
757     *
758     * @param mode
759     *            The mode to set.
760     */
761    public void setMode(final long mode) {
762        final long maskedMode = mode & S_IFMT;
763        switch ((int) maskedMode) {
764        case C_ISDIR:
765        case C_ISLNK:
766        case C_ISREG:
767        case C_ISFIFO:
768        case C_ISCHR:
769        case C_ISBLK:
770        case C_ISSOCK:
771        case C_ISNWK:
772            break;
773        default:
774            throw new IllegalArgumentException(
775                                               "Unknown mode. "
776                                               + "Full: " + Long.toHexString(mode)
777                                               + " Masked: " + Long.toHexString(maskedMode));
778        }
779
780        this.mode = mode;
781    }
782
783    /**
784     * Set the name.
785     *
786     * @param name
787     *            The name to set.
788     */
789    public void setName(final String name) {
790        this.name = name;
791    }
792
793    /**
794     * Set the number of links.
795     *
796     * @param nlink
797     *            The number of links to set.
798     */
799    public void setNumberOfLinks(final long nlink) {
800        this.nlink = nlink;
801    }
802
803    /**
804     * Set the remote device id.
805     *
806     * @param device
807     *            The remote device id to set.
808     * @throws UnsupportedOperationException
809     *             if this method is called for a CpioArchiveEntry with a new
810     *             format.
811     */
812    public void setRemoteDevice(final long device) {
813        checkOldFormat();
814        this.rmin = device;
815    }
816
817    /**
818     * Set the remote major device id.
819     *
820     * @param rmaj
821     *            The remote major device id to set.
822     * @throws UnsupportedOperationException
823     *             if this method is called for a CpioArchiveEntry with an old
824     *             format.
825     */
826    public void setRemoteDeviceMaj(final long rmaj) {
827        checkNewFormat();
828        this.rmaj = rmaj;
829    }
830
831    /**
832     * Set the remote minor device id.
833     *
834     * @param rmin
835     *            The remote minor device id to set.
836     * @throws UnsupportedOperationException
837     *             if this method is called for a CpioArchiveEntry with an old
838     *             format.
839     */
840    public void setRemoteDeviceMin(final long rmin) {
841        checkNewFormat();
842        this.rmin = rmin;
843    }
844
845    /**
846     * Set the time in seconds.
847     *
848     * @param time
849     *            The time to set.
850     */
851    public void setTime(final long time) {
852        this.mtime = time;
853    }
854
855    /**
856     * Set the user id.
857     *
858     * @param uid
859     *            The user id to set.
860     */
861    public void setUID(final long uid) {
862        this.uid = uid;
863    }
864
865    /* (non-Javadoc)
866     * @see java.lang.Object#hashCode()
867     */
868    @Override
869    public int hashCode() {
870        final int prime = 31;
871        int result = 1;
872        result = prime * result + (name == null ? 0 : name.hashCode());
873        return result;
874    }
875
876    /* (non-Javadoc)
877     * @see java.lang.Object#equals(java.lang.Object)
878     */
879    @Override
880    public boolean equals(final Object obj) {
881        if (this == obj) {
882            return true;
883        }
884        if (obj == null || getClass() != obj.getClass()) {
885            return false;
886        }
887        final CpioArchiveEntry other = (CpioArchiveEntry) obj;
888        if (name == null) {
889            return other.name == null;
890        } else {
891            return name.equals(other.name);
892        }
893    }
894}