1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.build.apkzlib.zip;
18 
19 import com.android.tools.build.apkzlib.utils.IOExceptionRunnable;
20 import java.io.IOException;
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23 
24 /**
25  * An extension of a {@link ZFile}. Extensions are notified when files are open, updated, closed and
26  * when files are added or removed from the zip. These notifications are received after the zip
27  * has been updated in memory for open, when files are added or removed and when the zip has been
28  * updated on disk or closed.
29  * <p>
30  * An extension is also notified before the file is updated, allowing it to modify the file before
31  * the update happens. If it does, then all extensions are notified of the changes on the zip file.
32  * Because the order of the notifications is preserved, all extensions are notified in the same
33  * order. For example, if two extensions E1 and E2 are registered and they both add a file at
34  * update time, this would be the flow:
35  * <ul>
36  *     <li>E1 receives {@code beforeUpdate} notification.</li>
37  *     <li>E1 adds file F1 to the zip (notifying the addition is suspended because another
38  *     notification is in progress).</li>
39  *     <li>E2 receives {@code beforeUpdate} notification.</li>
40  *     <li>E2 adds file F2 to the zip (notifying the addition is suspended because another
41  *     notification is in progress).</li>
42  *     <li>E1 is notified F1 was added.</li>
43  *     <li>E2 is notified F1 was added.</li>
44  *     <li>E1 is notified F2 was added.</li>
45  *     <li>E2 is notified F2 was added.</li>
46  *     <li>(zip file is updated on disk)</li>
47  *     <li>E1 is notified the zip was updated.</li>
48  *     <li>E2 is notified the zip was updated.</li>
49  * </ul>
50  * <p>
51  * An extension should not modify the zip file when notified of changes. If allowed, this would
52  * break event notification order in case multiple extensions are registered with the zip file.
53  * To allow performing changes to the zip file, all notification method return a
54  * {@code IOExceptionRunnable} that is invoked when {@link ZFile} has finished notifying all
55  * extensions.
56  */
57 public abstract class ZFileExtension {
58 
59     /**
60      * The zip file has been open and the zip's contents have been read. The default implementation
61      * does nothing and returns {@code null}.
62      *
63      * @return an optional runnable to run when notification of all listeners has ended
64      * @throws IOException failed to process the event
65      */
66     @Nullable
open()67     public IOExceptionRunnable open() throws IOException {
68         return null;
69     }
70 
71     /**
72      * The zip will be updated. This method allows the extension to register changes to the zip
73      * file before the file is written. The default implementation does nothing and returns
74      * {@code null}.
75      * <p>
76      * After this notification is received, the extension will receive further
77      * {@link #added(StoredEntry, StoredEntry)} and {@link #removed(StoredEntry)} notifications if
78      * it or other extensions add or remove files before update.
79      * <p>
80      * When no more files are updated, the {@link #entriesWritten()} notification is sent.
81      *
82      * @return an optional runnable to run when notification of all listeners has ended
83      * @throws IOException failed to process the event
84      */
85     @Nullable
beforeUpdate()86     public IOExceptionRunnable beforeUpdate() throws IOException {
87         return null;
88     }
89 
90     /**
91      * This notification is sent when all entries have been written in the file but the central
92      * directory and the EOCD have not yet been written. No entries should be added, removed or
93      * updated during this notification. If this method forces an update of either the central
94      * directory or EOCD, then this method will be invoked again for all extensions with the new
95      * central directory and EOCD.
96      * <p>
97      * After this notification, {@link #updated()} is sent.
98      *
99      * @throws IOException failed to process the event
100      */
entriesWritten()101     public void entriesWritten() throws IOException {
102     }
103 
104     /**
105      * The zip file has been updated on disk. The default implementation does nothing.
106      *
107      * @throws IOException failed to perform update tasks
108      */
updated()109     public void updated() throws IOException {
110     }
111 
112     /**
113      * The zip file has been closed. Note that if {@link ZFile#close()} requires that the zip file
114      * be updated (because it had in-memory changes), {@link #updated()} will be called before
115      * this method. The default implementation does nothing.
116      */
closed()117     public void closed() {
118     }
119 
120     /**
121      * A new entry has been added to the zip, possibly replacing an entry in there. The
122      * default implementation does nothing and returns {@code null}.
123      *
124      * @param entry the entry that was added
125      * @param replaced the entry that was replaced, if any
126      * @return an optional runnable to run when notification of all listeners has ended
127      */
128     @Nullable
added(@onnull StoredEntry entry, @Nullable StoredEntry replaced)129     public IOExceptionRunnable added(@Nonnull StoredEntry entry, @Nullable StoredEntry replaced) {
130         return null;
131     }
132 
133     /**
134      * An entry has been removed from the zip. This method is not invoked for entries that have
135      * been replaced. Those entries are notified using <em>replaced</em> in
136      * {@link #added(StoredEntry, StoredEntry)}. The default implementation does nothing and
137      * returns {@code null}.
138      *
139      * @param entry the entry that was deleted
140      * @return an optional runnable to run when notification of all listeners has ended
141      */
142     @Nullable
removed(@onnull StoredEntry entry)143     public IOExceptionRunnable removed(@Nonnull StoredEntry entry) {
144         return null;
145     }
146 }
147