OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libjava/] [classpath/] [java/] [util/] [zip/] [ZipFile.java] - Blame information for rev 771

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 771 jeremybenn
/* ZipFile.java --
2
   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006
3
   Free Software Foundation, Inc.
4
 
5
This file is part of GNU Classpath.
6
 
7
GNU Classpath is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2, or (at your option)
10
any later version.
11
 
12
GNU Classpath is distributed in the hope that it will be useful, but
13
WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
General Public License for more details.
16
 
17
You should have received a copy of the GNU General Public License
18
along with GNU Classpath; see the file COPYING.  If not, write to the
19
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
02110-1301 USA.
21
 
22
Linking this library statically or dynamically with other modules is
23
making a combined work based on this library.  Thus, the terms and
24
conditions of the GNU General Public License cover the whole
25
combination.
26
 
27
As a special exception, the copyright holders of this library give you
28
permission to link this library with independent modules to produce an
29
executable, regardless of the license terms of these independent
30
modules, and to copy and distribute the resulting executable under
31
terms of your choice, provided that you also meet, for each linked
32
independent module, the terms and conditions of the license of that
33
module.  An independent module is a module which is not derived from
34
or based on this library.  If you modify this library, you may extend
35
this exception to your version of the library, but you are not
36
obligated to do so.  If you do not wish to do so, delete this
37
exception statement from your version. */
38
 
39
 
40
package java.util.zip;
41
 
42
import gnu.java.util.EmptyEnumeration;
43
 
44
import java.io.EOFException;
45
import java.io.File;
46
import java.io.FileNotFoundException;
47
import java.io.IOException;
48
import java.io.InputStream;
49
import java.io.RandomAccessFile;
50
import java.io.UnsupportedEncodingException;
51
import java.nio.ByteBuffer;
52
import java.nio.charset.Charset;
53
import java.nio.charset.CharsetDecoder;
54
import java.util.Enumeration;
55
import java.util.Iterator;
56
import java.util.LinkedHashMap;
57
 
58
/**
59
 * This class represents a Zip archive.  You can ask for the contained
60
 * entries, or get an input stream for a file entry.  The entry is
61
 * automatically decompressed.
62
 *
63
 * This class is thread safe:  You can open input streams for arbitrary
64
 * entries in different threads.
65
 *
66
 * @author Jochen Hoenicke
67
 * @author Artur Biesiadowski
68
 */
69
public class ZipFile implements ZipConstants
70
{
71
 
72
  /**
73
   * Mode flag to open a zip file for reading.
74
   */
75
  public static final int OPEN_READ = 0x1;
76
 
77
  /**
78
   * Mode flag to delete a zip file after reading.
79
   */
80
  public static final int OPEN_DELETE = 0x4;
81
 
82
  /**
83
   * This field isn't defined in the JDK's ZipConstants, but should be.
84
   */
85
  static final int ENDNRD =  4;
86
 
87
  // Name of this zip file.
88
  private final String name;
89
 
90
  // File from which zip entries are read.
91
  private final RandomAccessFile raf;
92
 
93
  // The entries of this zip file when initialized and not yet closed.
94
  private LinkedHashMap<String, ZipEntry> entries;
95
 
96
  private boolean closed = false;
97
 
98
 
99
  /**
100
   * Helper function to open RandomAccessFile and throw the proper
101
   * ZipException in case opening the file fails.
102
   *
103
   * @param name the file name, or null if file is provided
104
   *
105
   * @param file the file, or null if name is provided
106
   *
107
   * @return the newly open RandomAccessFile, never null
108
   */
109
  private RandomAccessFile openFile(String name,
110
                                    File file)
111
    throws ZipException, IOException
112
  {
113
    try
114
      {
115
        return
116
          (name != null)
117
          ? new RandomAccessFile(name, "r")
118
          : new RandomAccessFile(file, "r");
119
      }
120
    catch (FileNotFoundException f)
121
      {
122
        ZipException ze = new ZipException(f.getMessage());
123
        ze.initCause(f);
124
        throw ze;
125
      }
126
  }
127
 
128
 
129
  /**
130
   * Opens a Zip file with the given name for reading.
131
   * @exception IOException if a i/o error occured.
132
   * @exception ZipException if the file doesn't contain a valid zip
133
   * archive.
134
   */
135
  public ZipFile(String name) throws ZipException, IOException
136
  {
137
    this.raf = openFile(name,null);
138
    this.name = name;
139
    checkZipFile();
140
  }
141
 
142
  /**
143
   * Opens a Zip file reading the given File.
144
   * @exception IOException if a i/o error occured.
145
   * @exception ZipException if the file doesn't contain a valid zip
146
   * archive.
147
   */
148
  public ZipFile(File file) throws ZipException, IOException
149
  {
150
    this.raf = openFile(null,file);
151
    this.name = file.getPath();
152
    checkZipFile();
153
  }
154
 
155
  /**
156
   * Opens a Zip file reading the given File in the given mode.
157
   *
158
   * If the OPEN_DELETE mode is specified, the zip file will be deleted at
159
   * some time moment after it is opened. It will be deleted before the zip
160
   * file is closed or the Virtual Machine exits.
161
   *
162
   * The contents of the zip file will be accessible until it is closed.
163
   *
164
   * @since JDK1.3
165
   * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
166
   *
167
   * @exception IOException if a i/o error occured.
168
   * @exception ZipException if the file doesn't contain a valid zip
169
   * archive.
170
   */
171
  public ZipFile(File file, int mode) throws ZipException, IOException
172
  {
173
    if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
174
      throw new IllegalArgumentException("invalid mode");
175
    if ((mode & OPEN_DELETE) != 0)
176
      file.deleteOnExit();
177
    this.raf = openFile(null,file);
178
    this.name = file.getPath();
179
    checkZipFile();
180
  }
181
 
182
  private void checkZipFile() throws ZipException
183
  {
184
    boolean valid = false;
185
 
186
    try
187
      {
188
        byte[] buf = new byte[4];
189
        raf.readFully(buf);
190
        int sig = buf[0] & 0xFF
191
                | ((buf[1] & 0xFF) << 8)
192
                | ((buf[2] & 0xFF) << 16)
193
                | ((buf[3] & 0xFF) << 24);
194
        valid = sig == LOCSIG;
195
      }
196
    catch (IOException _)
197
      {
198
      }
199
 
200
    if (!valid)
201
      {
202
        try
203
          {
204
            raf.close();
205
          }
206
        catch (IOException _)
207
          {
208
          }
209
        throw new ZipException("Not a valid zip file");
210
      }
211
  }
212
 
213
  /**
214
   * Checks if file is closed and throws an exception.
215
   */
216
  private void checkClosed()
217
  {
218
    if (closed)
219
      throw new IllegalStateException("ZipFile has closed: " + name);
220
  }
221
 
222
  /**
223
   * Read the central directory of a zip file and fill the entries
224
   * array.  This is called exactly once when first needed. It is called
225
   * while holding the lock on <code>raf</code>.
226
   *
227
   * @exception IOException if a i/o error occured.
228
   * @exception ZipException if the central directory is malformed
229
   */
230
  private void readEntries() throws ZipException, IOException
231
  {
232
    /* Search for the End Of Central Directory.  When a zip comment is
233
     * present the directory may start earlier.
234
     * Note that a comment has a maximum length of 64K, so that is the
235
     * maximum we search backwards.
236
     */
237
    PartialInputStream inp = new PartialInputStream(raf, 4096);
238
    long pos = raf.length() - ENDHDR;
239
    long top = Math.max(0, pos - 65536);
240
    do
241
      {
242
        if (pos < top)
243
          throw new ZipException
244
            ("central directory not found, probably not a zip file: " + name);
245
        inp.seek(pos--);
246
      }
247
    while (inp.readLeInt() != ENDSIG);
248
 
249
    if (inp.skip(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
250
      throw new EOFException(name);
251
    int count = inp.readLeShort();
252
    if (inp.skip(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
253
      throw new EOFException(name);
254
    int centralOffset = inp.readLeInt();
255
 
256
    entries = new LinkedHashMap<String, ZipEntry> (count+count/2);
257
    inp.seek(centralOffset);
258
 
259
    for (int i = 0; i < count; i++)
260
      {
261
        if (inp.readLeInt() != CENSIG)
262
          throw new ZipException("Wrong Central Directory signature: " + name);
263
 
264
        inp.skip(6);
265
        int method = inp.readLeShort();
266
        int dostime = inp.readLeInt();
267
        int crc = inp.readLeInt();
268
        int csize = inp.readLeInt();
269
        int size = inp.readLeInt();
270
        int nameLen = inp.readLeShort();
271
        int extraLen = inp.readLeShort();
272
        int commentLen = inp.readLeShort();
273
        inp.skip(8);
274
        int offset = inp.readLeInt();
275
        String name = inp.readString(nameLen);
276
 
277
        ZipEntry entry = new ZipEntry(name);
278
        entry.setMethod(method);
279
        entry.setCrc(crc & 0xffffffffL);
280
        entry.setSize(size & 0xffffffffL);
281
        entry.setCompressedSize(csize & 0xffffffffL);
282
        entry.setDOSTime(dostime);
283
        if (extraLen > 0)
284
          {
285
            byte[] extra = new byte[extraLen];
286
            inp.readFully(extra);
287
            entry.setExtra(extra);
288
          }
289
        if (commentLen > 0)
290
          {
291
            entry.setComment(inp.readString(commentLen));
292
          }
293
        entry.offset = offset;
294
        entries.put(name, entry);
295
      }
296
  }
297
 
298
  /**
299
   * Closes the ZipFile.  This also closes all input streams given by
300
   * this class.  After this is called, no further method should be
301
   * called.
302
   *
303
   * @exception IOException if a i/o error occured.
304
   */
305
  public void close() throws IOException
306
  {
307
    RandomAccessFile raf = this.raf;
308
    if (raf == null)
309
      return;
310
 
311
    synchronized (raf)
312
      {
313
        closed = true;
314
        entries = null;
315
        raf.close();
316
      }
317
  }
318
 
319
  /**
320
   * Calls the <code>close()</code> method when this ZipFile has not yet
321
   * been explicitly closed.
322
   */
323
  protected void finalize() throws IOException
324
  {
325
    if (!closed && raf != null) close();
326
  }
327
 
328
  /**
329
   * Returns an enumeration of all Zip entries in this Zip file.
330
   *
331
   * @exception IllegalStateException when the ZipFile has already been closed
332
   */
333
  public Enumeration<? extends ZipEntry> entries()
334
  {
335
    checkClosed();
336
 
337
    try
338
      {
339
        return new ZipEntryEnumeration(getEntries().values().iterator());
340
      }
341
    catch (IOException ioe)
342
      {
343
        return new EmptyEnumeration<ZipEntry>();
344
      }
345
  }
346
 
347
  /**
348
   * Checks that the ZipFile is still open and reads entries when necessary.
349
   *
350
   * @exception IllegalStateException when the ZipFile has already been closed.
351
   * @exception IOException when the entries could not be read.
352
   */
353
  private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
354
  {
355
    synchronized(raf)
356
      {
357
        checkClosed();
358
 
359
        if (entries == null)
360
          readEntries();
361
 
362
        return entries;
363
      }
364
  }
365
 
366
  /**
367
   * Searches for a zip entry in this archive with the given name.
368
   *
369
   * @param name the name. May contain directory components separated by
370
   * slashes ('/').
371
   * @return the zip entry, or null if no entry with that name exists.
372
   *
373
   * @exception IllegalStateException when the ZipFile has already been closed
374
   */
375
  public ZipEntry getEntry(String name)
376
  {
377
    checkClosed();
378
 
379
    try
380
      {
381
        LinkedHashMap<String, ZipEntry> entries = getEntries();
382
        ZipEntry entry = entries.get(name);
383
        // If we didn't find it, maybe it's a directory.
384
        if (entry == null && !name.endsWith("/"))
385
          entry = entries.get(name + '/');
386
        return entry != null ? new ZipEntry(entry, name) : null;
387
      }
388
    catch (IOException ioe)
389
      {
390
        return null;
391
      }
392
  }
393
 
394
  /**
395
   * Creates an input stream reading the given zip entry as
396
   * uncompressed data.  Normally zip entry should be an entry
397
   * returned by getEntry() or entries().
398
   *
399
   * This implementation returns null if the requested entry does not
400
   * exist.  This decision is not obviously correct, however, it does
401
   * appear to mirror Sun's implementation, and it is consistant with
402
   * their javadoc.  On the other hand, the old JCL book, 2nd Edition,
403
   * claims that this should return a "non-null ZIP entry".  We have
404
   * chosen for now ignore the old book, as modern versions of Ant (an
405
   * important application) depend on this behaviour.  See discussion
406
   * in this thread:
407
   * http://gcc.gnu.org/ml/java-patches/2004-q2/msg00602.html
408
   *
409
   * @param entry the entry to create an InputStream for.
410
   * @return the input stream, or null if the requested entry does not exist.
411
   *
412
   * @exception IllegalStateException when the ZipFile has already been closed
413
   * @exception IOException if a i/o error occured.
414
   * @exception ZipException if the Zip archive is malformed.
415
   */
416
  public InputStream getInputStream(ZipEntry entry) throws IOException
417
  {
418
    checkClosed();
419
 
420
    LinkedHashMap<String, ZipEntry> entries = getEntries();
421
    String name = entry.getName();
422
    ZipEntry zipEntry = entries.get(name);
423
    if (zipEntry == null)
424
      return null;
425
 
426
    PartialInputStream inp = new PartialInputStream(raf, 1024);
427
    inp.seek(zipEntry.offset);
428
 
429
    if (inp.readLeInt() != LOCSIG)
430
      throw new ZipException("Wrong Local header signature: " + name);
431
 
432
    inp.skip(4);
433
 
434
    if (zipEntry.getMethod() != inp.readLeShort())
435
      throw new ZipException("Compression method mismatch: " + name);
436
 
437
    inp.skip(16);
438
 
439
    int nameLen = inp.readLeShort();
440
    int extraLen = inp.readLeShort();
441
    inp.skip(nameLen + extraLen);
442
 
443
    inp.setLength(zipEntry.getCompressedSize());
444
 
445
    int method = zipEntry.getMethod();
446
    switch (method)
447
      {
448
      case ZipOutputStream.STORED:
449
        return inp;
450
      case ZipOutputStream.DEFLATED:
451
        inp.addDummyByte();
452
        final Inflater inf = new Inflater(true);
453
        final int sz = (int) entry.getSize();
454
        return new InflaterInputStream(inp, inf)
455
        {
456
          public int available() throws IOException
457
          {
458
            if (sz == -1)
459
              return super.available();
460
            if (super.available() != 0)
461
              return sz - inf.getTotalOut();
462
            return 0;
463
          }
464
        };
465
      default:
466
        throw new ZipException("Unknown compression method " + method);
467
      }
468
  }
469
 
470
  /**
471
   * Returns the (path) name of this zip file.
472
   */
473
  public String getName()
474
  {
475
    return name;
476
  }
477
 
478
  /**
479
   * Returns the number of entries in this zip file.
480
   *
481
   * @exception IllegalStateException when the ZipFile has already been closed
482
   */
483
  public int size()
484
  {
485
    checkClosed();
486
 
487
    try
488
      {
489
        return getEntries().size();
490
      }
491
    catch (IOException ioe)
492
      {
493
        return 0;
494
      }
495
  }
496
 
497
  private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
498
  {
499
    private final Iterator<ZipEntry> elements;
500
 
501
    public ZipEntryEnumeration(Iterator<ZipEntry> elements)
502
    {
503
      this.elements = elements;
504
    }
505
 
506
    public boolean hasMoreElements()
507
    {
508
      return elements.hasNext();
509
    }
510
 
511
    public ZipEntry nextElement()
512
    {
513
      /* We return a clone, just to be safe that the user doesn't
514
       * change the entry.
515
       */
516
      return (ZipEntry) (elements.next().clone());
517
    }
518
  }
519
 
520
  private static final class PartialInputStream extends InputStream
521
  {
522
    /**
523
     * The UTF-8 charset use for decoding the filenames.
524
     */
525
    private static final Charset UTF8CHARSET = Charset.forName("UTF-8");
526
 
527
    /**
528
     * The actual UTF-8 decoder. Created on demand.
529
     */
530
    private CharsetDecoder utf8Decoder;
531
 
532
    private final RandomAccessFile raf;
533
    private final byte[] buffer;
534
    private long bufferOffset;
535
    private int pos;
536
    private long end;
537
    // We may need to supply an extra dummy byte to our reader.
538
    // See Inflater.  We use a count here to simplify the logic
539
    // elsewhere in this class.  Note that we ignore the dummy
540
    // byte in methods where we know it is not needed.
541
    private int dummyByteCount;
542
 
543
    public PartialInputStream(RandomAccessFile raf, int bufferSize)
544
      throws IOException
545
    {
546
      this.raf = raf;
547
      buffer = new byte[bufferSize];
548
      bufferOffset = -buffer.length;
549
      pos = buffer.length;
550
      end = raf.length();
551
    }
552
 
553
    void setLength(long length)
554
    {
555
      end = bufferOffset + pos + length;
556
    }
557
 
558
    private void fillBuffer() throws IOException
559
    {
560
      synchronized (raf)
561
        {
562
          long len = end - bufferOffset;
563
          if (len == 0 && dummyByteCount > 0)
564
            {
565
              buffer[0] = 0;
566
              dummyByteCount = 0;
567
            }
568
          else
569
            {
570
              raf.seek(bufferOffset);
571
              raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
572
            }
573
        }
574
    }
575
 
576
    public int available()
577
    {
578
      long amount = end - (bufferOffset + pos);
579
      if (amount > Integer.MAX_VALUE)
580
        return Integer.MAX_VALUE;
581
      return (int) amount;
582
    }
583
 
584
    public int read() throws IOException
585
    {
586
      if (bufferOffset + pos >= end + dummyByteCount)
587
        return -1;
588
      if (pos == buffer.length)
589
        {
590
          bufferOffset += buffer.length;
591
          pos = 0;
592
          fillBuffer();
593
        }
594
 
595
      return buffer[pos++] & 0xFF;
596
    }
597
 
598
    public int read(byte[] b, int off, int len) throws IOException
599
    {
600
      if (len > end + dummyByteCount - (bufferOffset + pos))
601
        {
602
          len = (int) (end + dummyByteCount - (bufferOffset + pos));
603
          if (len == 0)
604
            return -1;
605
        }
606
 
607
      int totalBytesRead = Math.min(buffer.length - pos, len);
608
      System.arraycopy(buffer, pos, b, off, totalBytesRead);
609
      pos += totalBytesRead;
610
      off += totalBytesRead;
611
      len -= totalBytesRead;
612
 
613
      while (len > 0)
614
        {
615
          bufferOffset += buffer.length;
616
          pos = 0;
617
          fillBuffer();
618
          int remain = Math.min(buffer.length, len);
619
          System.arraycopy(buffer, pos, b, off, remain);
620
          pos += remain;
621
          off += remain;
622
          len -= remain;
623
          totalBytesRead += remain;
624
        }
625
 
626
      return totalBytesRead;
627
    }
628
 
629
    public long skip(long amount) throws IOException
630
    {
631
      if (amount < 0)
632
        return 0;
633
      if (amount > end - (bufferOffset + pos))
634
        amount = end - (bufferOffset + pos);
635
      seek(bufferOffset + pos + amount);
636
      return amount;
637
    }
638
 
639
    void seek(long newpos) throws IOException
640
    {
641
      long offset = newpos - bufferOffset;
642
      if (offset >= 0 && offset <= buffer.length)
643
        {
644
          pos = (int) offset;
645
        }
646
      else
647
        {
648
          bufferOffset = newpos;
649
          pos = 0;
650
          fillBuffer();
651
        }
652
    }
653
 
654
    void readFully(byte[] buf) throws IOException
655
    {
656
      if (read(buf, 0, buf.length) != buf.length)
657
        throw new EOFException();
658
    }
659
 
660
    void readFully(byte[] buf, int off, int len) throws IOException
661
    {
662
      if (read(buf, off, len) != len)
663
        throw new EOFException();
664
    }
665
 
666
    int readLeShort() throws IOException
667
    {
668
      int result;
669
      if(pos + 1 < buffer.length)
670
        {
671
          result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
672
          pos += 2;
673
        }
674
      else
675
        {
676
          int b0 = read();
677
          int b1 = read();
678
          if (b1 == -1)
679
            throw new EOFException();
680
          result = (b0 & 0xff) | (b1 & 0xff) << 8;
681
        }
682
      return result;
683
    }
684
 
685
    int readLeInt() throws IOException
686
    {
687
      int result;
688
      if(pos + 3 < buffer.length)
689
        {
690
          result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
691
                   | ((buffer[pos + 2] & 0xff)
692
                       | (buffer[pos + 3] & 0xff) << 8) << 16);
693
          pos += 4;
694
        }
695
      else
696
        {
697
          int b0 = read();
698
          int b1 = read();
699
          int b2 = read();
700
          int b3 = read();
701
          if (b3 == -1)
702
            throw new EOFException();
703
          result =  (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
704
                    | (b3 & 0xff) << 8) << 16);
705
        }
706
      return result;
707
    }
708
 
709
    /**
710
     * Decode chars from byte buffer using UTF8 encoding.  This
711
     * operation is performance-critical since a jar file contains a
712
     * large number of strings for the name of each file in the
713
     * archive.  This routine therefore avoids using the expensive
714
     * utf8Decoder when decoding is straightforward.
715
     *
716
     * @param buffer the buffer that contains the encoded character
717
     *        data
718
     * @param pos the index in buffer of the first byte of the encoded
719
     *        data
720
     * @param length the length of the encoded data in number of
721
     *        bytes.
722
     *
723
     * @return a String that contains the decoded characters.
724
     */
725
    private String decodeChars(byte[] buffer, int pos, int length)
726
      throws IOException
727
    {
728
      String result;
729
      int i=length - 1;
730
      while ((i >= 0) && (buffer[i] <= 0x7f))
731
        {
732
          i--;
733
        }
734
      if (i < 0)
735
        {
736
          result = new String(buffer, 0, pos, length);
737
        }
738
      else
739
        {
740
          ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
741
          if (utf8Decoder == null)
742
            utf8Decoder = UTF8CHARSET.newDecoder();
743
          utf8Decoder.reset();
744
          char [] characters = utf8Decoder.decode(bufferBuffer).array();
745
          result = String.valueOf(characters);
746
        }
747
      return result;
748
    }
749
 
750
    String readString(int length) throws IOException
751
    {
752
      if (length > end - (bufferOffset + pos))
753
        throw new EOFException();
754
 
755
      String result = null;
756
      try
757
        {
758
          if (buffer.length - pos >= length)
759
            {
760
              result = decodeChars(buffer, pos, length);
761
              pos += length;
762
            }
763
          else
764
            {
765
              byte[] b = new byte[length];
766
              readFully(b);
767
              result = decodeChars(b, 0, length);
768
            }
769
        }
770
      catch (UnsupportedEncodingException uee)
771
        {
772
          throw new AssertionError(uee);
773
        }
774
      return result;
775
    }
776
 
777
    public void addDummyByte()
778
    {
779
      dummyByteCount = 1;
780
    }
781
  }
782
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.